diff options
author | jk7744.park <jk7744.park@samsung.com> | 2015-10-24 16:49:08 +0900 |
---|---|---|
committer | jk7744.park <jk7744.park@samsung.com> | 2015-10-24 16:49:08 +0900 |
commit | 32e864f0f32b7628d995e84d07646b01dc5bf2d5 (patch) | |
tree | 27f97eaa1ca7e171815e4f88b348448b05f02748 /tests/check/elements | |
parent | 9093d777f57b3f1dc62010bb8e0c7ed3d72a76d2 (diff) | |
download | gst-plugins-good-accepted/tizen_2.4_mobile.tar.gz gst-plugins-good-accepted/tizen_2.4_mobile.tar.bz2 gst-plugins-good-accepted/tizen_2.4_mobile.zip |
tizen 2.4 releasetizen_2.4_mobile_releasesubmit/tizen_2.4/20151028.064339accepted/tizen/2.4/mobile/20151029.032704accepted/tizen_2.4_mobile
Diffstat (limited to 'tests/check/elements')
75 files changed, 35251 insertions, 0 deletions
diff --git a/tests/check/elements/aacparse.c b/tests/check/elements/aacparse.c new file mode 100755 index 0000000..9a50647 --- /dev/null +++ b/tests/check/elements/aacparse.c @@ -0,0 +1,291 @@ +/* + * GStreamer + * + * unit test for aacparse + * + * Copyright (C) 2008 Nokia Corporation. All rights reserved. + * + * Contact: Stefan Kost <stefan.kost@nokia.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. + */ + +#include <gst/check/gstcheck.h> +#include "parser.h" + +#define SRC_CAPS_CDATA "audio/mpeg, mpegversion=(int)4, framed=(boolean)false, codec_data=(buffer)1190" +#define SRC_CAPS_TMPL "audio/mpeg, framed=(boolean)false, mpegversion=(int){2,4}" + +#define SINK_CAPS \ + "audio/mpeg, framed=(boolean)true" +#define SINK_CAPS_MPEG2 \ + "audio/mpeg, framed=(boolean)true, mpegversion=2, rate=48000, channels=2" +#define SINK_CAPS_MPEG4 \ + "audio/mpeg, framed=(boolean)true, mpegversion=4, rate=96000, channels=2" +#define SINK_CAPS_TMPL "audio/mpeg, framed=(boolean)true, mpegversion=(int){2,4}" + +GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (SINK_CAPS_TMPL) + ); + +GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (SRC_CAPS_TMPL) + ); + +/* some data */ +static guint8 adif_header[] = { + 'A', 'D', 'I', 'F' +}; + +static guint8 adts_frame_mpeg2[] = { + 0xff, 0xf9, 0x4c, 0x80, 0x01, 0xff, 0xfc, 0x21, 0x10, 0xd3, 0x20, 0x0c, + 0x32, 0x00, 0xc7 +}; + +static guint8 adts_frame_mpeg4[] = { + 0xff, 0xf1, 0x4c, 0x80, 0x01, 0xff, 0xfc, 0x21, 0x10, 0xd3, 0x20, 0x0c, + 0x32, 0x00, 0xc7 +}; + +static guint8 garbage_frame[] = { + 0xff, 0xff, 0xff, 0xff, 0xff +}; + +/* + * Test if the parser pushes data with ADIF header properly and detects the + * stream to MPEG4 properly. + */ +GST_START_TEST (test_parse_adif_normal) +{ + GstParserTest ptest; + + /* ADIF header */ + gst_parser_test_init (&ptest, adif_header, sizeof (adif_header), 1); + /* well, no garbage, followed by random data */ + ptest.series[2].size = 100; + ptest.series[2].num = 3; + /* and we do not really expect output frames */ + ptest.framed = FALSE; + /* Check that the negotiated caps are as expected */ + /* For ADIF parser assumes that data is always version 4 */ + ptest.sink_caps = + gst_caps_from_string (SINK_CAPS_MPEG4 ", stream-format=(string)adif"); + + gst_parser_test_run (&ptest, NULL); + + gst_caps_unref (ptest.sink_caps); +} + +GST_END_TEST; + + +GST_START_TEST (test_parse_adts_normal) +{ + gst_parser_test_normal (adts_frame_mpeg4, sizeof (adts_frame_mpeg4)); +} + +GST_END_TEST; + + +GST_START_TEST (test_parse_adts_drain_single) +{ + gst_parser_test_drain_single (adts_frame_mpeg4, sizeof (adts_frame_mpeg4)); +} + +GST_END_TEST; + + +GST_START_TEST (test_parse_adts_drain_garbage) +{ + gst_parser_test_drain_garbage (adts_frame_mpeg4, sizeof (adts_frame_mpeg4), + garbage_frame, sizeof (garbage_frame)); +} + +GST_END_TEST; + + +GST_START_TEST (test_parse_adts_split) +{ + gst_parser_test_split (adts_frame_mpeg4, sizeof (adts_frame_mpeg4)); +} + +GST_END_TEST; + + +GST_START_TEST (test_parse_adts_skip_garbage) +{ + gst_parser_test_skip_garbage (adts_frame_mpeg4, sizeof (adts_frame_mpeg4), + garbage_frame, sizeof (garbage_frame)); +} + +GST_END_TEST; + + +/* + * Test if the src caps are set according to stream format (MPEG version). + */ +GST_START_TEST (test_parse_adts_detect_mpeg_version) +{ + gst_parser_test_output_caps (adts_frame_mpeg2, sizeof (adts_frame_mpeg2), + NULL, + SINK_CAPS_MPEG2 + ", stream-format=(string)adts, level=(string)2, profile=(string)lc"); +} + +GST_END_TEST; + +#define structure_get_int(s,f) \ + (g_value_get_int(gst_structure_get_value(s,f))) +#define fail_unless_structure_field_int_equals(s,field,num) \ + fail_unless_equals_int (structure_get_int(s,field), num) +/* + * Test if the parser handles raw stream and codec_data info properly. + */ +GST_START_TEST (test_parse_handle_codec_data) +{ + GstCaps *caps; + GstStructure *s; + const gchar *stream_format; + + /* Push random data. It should get through since the parser should be + * initialized because it got codec_data in the caps */ + caps = gst_parser_test_get_output_caps (NULL, 100, SRC_CAPS_CDATA); + fail_unless (caps != NULL); + + /* Check that the negotiated caps are as expected */ + /* When codec_data is present, parser assumes that data is version 4 */ + GST_LOG ("aac output caps: %" GST_PTR_FORMAT, caps); + s = gst_caps_get_structure (caps, 0); + fail_unless (gst_structure_has_name (s, "audio/mpeg")); + fail_unless_structure_field_int_equals (s, "mpegversion", 4); + fail_unless_structure_field_int_equals (s, "channels", 2); + fail_unless_structure_field_int_equals (s, "rate", 48000); + fail_unless (gst_structure_has_field (s, "codec_data")); + fail_unless (gst_structure_has_field (s, "stream-format")); + stream_format = gst_structure_get_string (s, "stream-format"); + fail_unless (strcmp (stream_format, "raw") == 0); + + gst_caps_unref (caps); +} + +GST_END_TEST; + +GST_START_TEST (test_parse_proxy_constraints) +{ + GstCaps *caps; + GstElement *parse, *filter; + GstPad *sinkpad; + GstStructure *s; + + parse = gst_element_factory_make ("aacparse", NULL); + filter = gst_element_factory_make ("capsfilter", NULL); + + /* constraint on rate and version */ + caps = gst_caps_from_string ("audio/mpeg,mpegversion=2,rate=44100"); + g_object_set (filter, "caps", caps, NULL); + gst_caps_unref (caps); + + gst_element_link (parse, filter); + + sinkpad = gst_element_get_static_pad (parse, "sink"); + caps = gst_pad_query_caps (sinkpad, NULL); + GST_LOG ("caps %" GST_PTR_FORMAT, caps); + + fail_unless (gst_caps_get_size (caps) == 1); + + /* getcaps should proxy the rate constraint */ + s = gst_caps_get_structure (caps, 0); + fail_unless (gst_structure_has_name (s, "audio/mpeg")); + fail_unless_structure_field_int_equals (s, "rate", 44100); + gst_caps_unref (caps); + + /* should accept without the constraint */ + caps = gst_caps_from_string ("audio/mpeg,mpegversion=2"); + fail_unless (gst_pad_query_accept_caps (sinkpad, caps)); + gst_caps_unref (caps); + + /* should not accept with conflicting version */ + caps = gst_caps_from_string ("audio/mpeg,mpegversion=4"); + fail_if (gst_pad_query_accept_caps (sinkpad, caps)); + gst_caps_unref (caps); + + gst_object_unref (sinkpad); + + gst_object_unref (filter); + gst_object_unref (parse); +} + +GST_END_TEST; + +static Suite * +aacparse_suite (void) +{ + Suite *s = suite_create ("aacparse"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + /* ADIF tests */ + tcase_add_test (tc_chain, test_parse_adif_normal); + + /* ADTS tests */ + tcase_add_test (tc_chain, test_parse_adts_normal); + tcase_add_test (tc_chain, test_parse_adts_drain_single); + tcase_add_test (tc_chain, test_parse_adts_drain_garbage); + tcase_add_test (tc_chain, test_parse_adts_split); + tcase_add_test (tc_chain, test_parse_adts_skip_garbage); + tcase_add_test (tc_chain, test_parse_adts_detect_mpeg_version); + + /* Other tests */ + tcase_add_test (tc_chain, test_parse_handle_codec_data); + + /* caps tests */ + tcase_add_test (tc_chain, test_parse_proxy_constraints); + + return s; +} + + +/* + * TODO: + * - Both push- and pull-modes need to be tested + * * Pull-mode & EOS + */ + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = aacparse_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + /* init test context */ + ctx_factory = "aacparse"; + ctx_sink_template = &sinktemplate; + ctx_src_template = &srctemplate; + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/ac3parse.c b/tests/check/elements/ac3parse.c new file mode 100644 index 0000000..7877cd0 --- /dev/null +++ b/tests/check/elements/ac3parse.c @@ -0,0 +1,163 @@ +/* + * GStreamer + * + * unit test for ac3parse + * + * Copyright (C) 2008 Nokia Corporation. All rights reserved. + * + * Contact: Stefan Kost <stefan.kost@nokia.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. + */ + +#include <gst/check/gstcheck.h> +#include "parser.h" + +#define SRC_CAPS_TMPL "audio/x-ac3, framed=(boolean)false" +#define SINK_CAPS_TMPL "audio/x-ac3, framed=(boolean)true" + +GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (SINK_CAPS_TMPL) + ); + +GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (SRC_CAPS_TMPL) + ); + +/* some data */ + +static guint8 ac3_frame[512] = { + 0x0b, 0x77, 0xb6, 0xa8, 0x10, 0x40, 0x2f, 0x84, + 0x29, 0xcb, 0xfe, 0x75, 0x7c, 0xf9, 0xf3, 0xe7, + 0xcf, 0x9f, 0x3e, 0x7c, 0xf9, 0xf3, 0xe7, 0xcf, + 0x9f, 0x3e, 0x7c, 0xf9, 0xf3, 0xe7, 0xcf, 0x9f, + 0x3e, 0x7c, 0xf9, 0xf3, 0xe7, 0xcf, 0x9f, 0x3e, + 0x7c, 0xf9, 0xf3, 0xe7, 0xcf, 0x9f, 0x3e, 0x7c, + 0xf9, 0xf3, 0xe7, 0xcf, 0x9f, 0x3e, 0x7c, 0xf9, + 0xf3, 0xe7, 0xcf, 0x9f, 0x3e, 0x7c, 0xf9, 0xf3, + 0xe7, 0xcf, 0x9f, 0x3e, 0x7c, 0xf9, 0xf3, 0xe7, + 0xcf, 0x9f, 0x3e, 0x32, 0xd3, 0xff, 0xc0, 0x06, + 0xe9, 0x40, 0x00, 0x6e, 0x94, 0x00, 0x06, 0xe9, + 0x40, 0x00, 0x6e, 0x94, 0x00, 0x06, 0xe9, 0x40, + 0x00, 0x6e, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static guint8 garbage_frame[] = { + 0xff, 0xff, 0xff, 0xff, 0xff +}; + + +GST_START_TEST (test_parse_normal) +{ + gst_parser_test_normal (ac3_frame, sizeof (ac3_frame)); +} + +GST_END_TEST; + + +GST_START_TEST (test_parse_drain_single) +{ + gst_parser_test_drain_single (ac3_frame, sizeof (ac3_frame)); +} + +GST_END_TEST; + + +GST_START_TEST (test_parse_drain_garbage) +{ + gst_parser_test_drain_garbage (ac3_frame, sizeof (ac3_frame), + garbage_frame, sizeof (garbage_frame)); +} + +GST_END_TEST; + + +GST_START_TEST (test_parse_split) +{ + gst_parser_test_split (ac3_frame, sizeof (ac3_frame)); +} + +GST_END_TEST; + + +GST_START_TEST (test_parse_skip_garbage) +{ + gst_parser_test_skip_garbage (ac3_frame, sizeof (ac3_frame), + garbage_frame, sizeof (garbage_frame)); +} + +GST_END_TEST; + + +GST_START_TEST (test_parse_detect_stream) +{ + gst_parser_test_output_caps (ac3_frame, sizeof (ac3_frame), + NULL, SINK_CAPS_TMPL ",channels=1,rate=48000,alignment=frame"); +} + +GST_END_TEST; + + +static Suite * +ac3parse_suite (void) +{ + Suite *s = suite_create ("ac3parse"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_parse_normal); + tcase_add_test (tc_chain, test_parse_drain_single); + tcase_add_test (tc_chain, test_parse_drain_garbage); + tcase_add_test (tc_chain, test_parse_split); + tcase_add_test (tc_chain, test_parse_skip_garbage); + tcase_add_test (tc_chain, test_parse_detect_stream); + + return s; +} + + +/* + * TODO: + * - Both push- and pull-modes need to be tested + * * Pull-mode & EOS + */ + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = ac3parse_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + /* init test context */ + ctx_factory = "ac3parse"; + ctx_sink_template = &sinktemplate; + ctx_src_template = &srctemplate; + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/alphacolor.c b/tests/check/elements/alphacolor.c new file mode 100644 index 0000000..15496d8 --- /dev/null +++ b/tests/check/elements/alphacolor.c @@ -0,0 +1,288 @@ +/* GStreamer unit test for the alphacolor element + * + * Copyright (C) 2007 Tim-Philipp Müller <tim centricular net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <gst/check/gstcheck.h> +#include <gst/video/video.h> + +/* 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 */ +GstPad *mysrcpad, *mysinkpad; + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("AYUV")) + ); +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ RGBA, RGB }")) + ); + +static GstElement * +setup_alphacolor (void) +{ + GstElement *alphacolor; + + alphacolor = gst_check_setup_element ("alphacolor"); + mysrcpad = gst_check_setup_src_pad (alphacolor, &srctemplate); + mysinkpad = gst_check_setup_sink_pad (alphacolor, &sinktemplate); + + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + + return alphacolor; +} + +static void +cleanup_alphacolor (GstElement * alphacolor) +{ + GST_DEBUG ("cleaning up"); + + gst_pad_set_active (mysrcpad, FALSE); + gst_pad_set_active (mysinkpad, FALSE); + gst_check_teardown_src_pad (alphacolor); + gst_check_teardown_sink_pad (alphacolor); + gst_check_teardown_element (alphacolor); +} + +#define WIDTH 3 +#define HEIGHT 4 + +static GstCaps * +create_caps_rgb24 (void) +{ + GstCaps *caps; + + caps = gst_caps_new_simple ("video/x-raw", + "width", G_TYPE_INT, 3, + "height", G_TYPE_INT, 4, + "framerate", GST_TYPE_FRACTION, 0, 1, + "format", G_TYPE_STRING, "RGB", NULL); + + return caps; +} + +static GstCaps * +create_caps_rgba32 (void) +{ + GstCaps *caps; + + caps = gst_caps_new_simple ("video/x-raw", + "width", G_TYPE_INT, 3, + "height", G_TYPE_INT, 4, + "framerate", GST_TYPE_FRACTION, 0, 1, + "format", G_TYPE_STRING, "RGBA", NULL); + + return caps; +} + +static GstBuffer * +create_buffer_rgb24_3x4 (void) +{ + /* stride is rounded up to multiple of 4, so 3 bytes padding for each row */ + const guint8 rgb24_3x4_img[HEIGHT * GST_ROUND_UP_4 (WIDTH * 3)] = { + 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00 + }; + guint rowstride = GST_ROUND_UP_4 (WIDTH * 3); + GstBuffer *buf; + GstMapInfo info; + + buf = gst_buffer_new_and_alloc (HEIGHT * rowstride); + gst_buffer_map (buf, &info, GST_MAP_READWRITE); + fail_unless_equals_int (info.size, sizeof (rgb24_3x4_img)); + memcpy (info.data, rgb24_3x4_img, sizeof (rgb24_3x4_img)); + + gst_buffer_unmap (buf, &info); + + return buf; +} + +static GstBuffer * +create_buffer_rgba32_3x4 (void) +{ + /* stride is rounded up to multiple of 4, so 3 bytes padding for each row */ + /* should be: RED BLUE WHITE where 'nothing' is fully transparent + * GREEN RED BLUE and all other colours are fully + * NOTHING GREEN RED opaque. + * BLACK NOTHING GREEN + */ + const guint8 rgba32_3x4_img[HEIGHT * WIDTH * 4] = { + 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff + }; + guint rowstride = WIDTH * 4; + GstBuffer *buf; + GstMapInfo map; + + buf = gst_buffer_new_and_alloc (HEIGHT * rowstride); + gst_buffer_map (buf, &map, GST_MAP_READWRITE); + fail_unless_equals_int (map.size, sizeof (rgba32_3x4_img)); + memcpy (map.data, rgba32_3x4_img, sizeof (rgba32_3x4_img)); + + gst_buffer_unmap (buf, &map); + + return buf; +} + +GST_START_TEST (test_rgb24) +{ + GstElement *alphacolor; + GstBuffer *inbuffer; + GstCaps *incaps; + + incaps = create_caps_rgb24 (); + alphacolor = setup_alphacolor (); + + fail_unless_equals_int (gst_element_set_state (alphacolor, GST_STATE_PLAYING), + GST_STATE_CHANGE_SUCCESS); + + gst_check_setup_events (mysrcpad, alphacolor, incaps, GST_FORMAT_TIME); + + inbuffer = create_buffer_rgb24_3x4 (); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away reference; this should error out with a not-negotiated + * error, alphacolor should only accept RGBA caps, not but plain RGB24 caps */ + GST_DEBUG ("push it"); + fail_unless_equals_int (gst_pad_push (mysrcpad, inbuffer), + GST_FLOW_NOT_NEGOTIATED); + GST_DEBUG ("pushed it"); + + fail_unless (g_list_length (buffers) == 0); + + fail_unless_equals_int (gst_element_set_state (alphacolor, GST_STATE_NULL), + GST_STATE_CHANGE_SUCCESS); + + /* cleanup */ + GST_DEBUG ("cleanup alphacolor"); + cleanup_alphacolor (alphacolor); + GST_DEBUG ("cleanup, unref incaps"); + ASSERT_CAPS_REFCOUNT (incaps, "incaps", 1); + gst_caps_unref (incaps); +} + +GST_END_TEST; + +/* these macros assume WIDTH and HEIGHT is fixed to what's defined above */ +#define fail_unless_ayuv_pixel_has_alpha(ayuv,x,y,a) \ + { \ + guint8 *pixel; \ + pixel = ((guint8*)(ayuv) + ((WIDTH * 4) * (y)) + ((x) * 4)); \ + fail_unless_equals_int (pixel[0], a); \ + } + +GST_START_TEST (test_rgba32) +{ + GstElement *alphacolor; + GstBuffer *inbuffer; + GstBuffer *outbuffer; + GstCaps *incaps; + guint8 *ayuv; + guint outlength; + GstMapInfo map; + + incaps = create_caps_rgba32 (); + alphacolor = setup_alphacolor (); + + fail_unless_equals_int (gst_element_set_state (alphacolor, GST_STATE_PLAYING), + GST_STATE_CHANGE_SUCCESS); + + gst_check_setup_events (mysrcpad, alphacolor, incaps, GST_FORMAT_TIME); + + inbuffer = create_buffer_rgba32_3x4 (); + GST_DEBUG ("Created buffer of %" G_GSIZE_FORMAT " bytes", + gst_buffer_get_size (inbuffer)); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away reference */ + GST_DEBUG ("push it"); + fail_unless_equals_int (gst_pad_push (mysrcpad, inbuffer), GST_FLOW_OK); + GST_DEBUG ("pushed it"); + + /* ... and puts a new buffer on the global list */ + fail_unless (g_list_length (buffers) == 1); + outbuffer = (GstBuffer *) buffers->data; + fail_if (outbuffer == NULL); + fail_unless (GST_IS_BUFFER (outbuffer)); + + ASSERT_BUFFER_REFCOUNT (outbuffer, "outbuffer", 1); + outlength = WIDTH * HEIGHT * 4; /* output is AYUV */ + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + fail_unless_equals_int (map.size, outlength); + + ayuv = map.data; + + /* check alpha values (0x00 = totally transparent, 0xff = totally opaque) */ + fail_unless_ayuv_pixel_has_alpha (ayuv, 0, 0, 0xff); + fail_unless_ayuv_pixel_has_alpha (ayuv, 1, 0, 0xff); + fail_unless_ayuv_pixel_has_alpha (ayuv, 2, 0, 0xff); + fail_unless_ayuv_pixel_has_alpha (ayuv, 0, 1, 0xff); + fail_unless_ayuv_pixel_has_alpha (ayuv, 1, 1, 0xff); + fail_unless_ayuv_pixel_has_alpha (ayuv, 2, 1, 0xff); + fail_unless_ayuv_pixel_has_alpha (ayuv, 0, 2, 0x00); + fail_unless_ayuv_pixel_has_alpha (ayuv, 1, 2, 0xff); + fail_unless_ayuv_pixel_has_alpha (ayuv, 2, 2, 0xff); + fail_unless_ayuv_pixel_has_alpha (ayuv, 0, 3, 0xff); + fail_unless_ayuv_pixel_has_alpha (ayuv, 1, 3, 0x00); + fail_unless_ayuv_pixel_has_alpha (ayuv, 2, 3, 0xff); + + /* we don't check the YUV data, because apparently results differ slightly + * depending on whether we run in valgrind or not */ + + gst_buffer_unmap (outbuffer, &map); + + buffers = g_list_remove (buffers, outbuffer); + gst_buffer_unref (outbuffer); + + fail_unless_equals_int (gst_element_set_state (alphacolor, GST_STATE_NULL), + GST_STATE_CHANGE_SUCCESS); + + /* cleanup */ + GST_DEBUG ("cleanup alphacolor"); + cleanup_alphacolor (alphacolor); + GST_DEBUG ("cleanup, unref incaps"); + ASSERT_CAPS_REFCOUNT (incaps, "incaps", 1); + gst_caps_unref (incaps); +} + +GST_END_TEST; + + +static Suite * +alphacolor_suite (void) +{ + Suite *s = suite_create ("alphacolor"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_rgb24); + tcase_add_test (tc_chain, test_rgba32); + + return s; +} + +GST_CHECK_MAIN (alphacolor); diff --git a/tests/check/elements/amrparse.c b/tests/check/elements/amrparse.c new file mode 100644 index 0000000..ee5b455 --- /dev/null +++ b/tests/check/elements/amrparse.c @@ -0,0 +1,327 @@ +/* + * GStreamer + * + * unit test for amrparse + * + * Copyright (C) 2008 Nokia Corporation. All rights reserved. + * + * Contact: Stefan Kost <stefan.kost@nokia.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. + */ + +#include <gst/check/gstcheck.h> +#include "parser.h" + +#define SRC_CAPS_NB "audio/x-amr-nb-sh" +#define SRC_CAPS_WB "audio/x-amr-wb-sh" +#define SRC_CAPS_ANY "ANY" + +#define SINK_CAPS_NB "audio/AMR, rate=8000 , channels=1" +#define SINK_CAPS_WB "audio/AMR-WB, rate=16000 , channels=1" +#define SINK_CAPS_ANY "ANY" + +#define AMR_FRAME_DURATION (GST_SECOND/50) + +static GstStaticPadTemplate sinktemplate_nb = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (SINK_CAPS_NB) + ); + +static GstStaticPadTemplate sinktemplate_wb = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (SINK_CAPS_WB) + ); + +static GstStaticPadTemplate srctemplate_nb = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (SRC_CAPS_NB) + ); + +static GstStaticPadTemplate srctemplate_wb = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (SRC_CAPS_WB) + ); + + +/* some data */ + +static guint8 frame_data_nb[] = { + 0x0c, 0x56, 0x3c, 0x52, 0xe0, 0x61, 0xbc, 0x45, + 0x0f, 0x98, 0x2e, 0x01, 0x42, 0x02 +}; + +static guint8 frame_data_wb[] = { + 0x08, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 +}; + +static guint8 frame_hdr_nb[] = { + '#', '!', 'A', 'M', 'R', '\n' +}; + +static guint8 frame_hdr_wb[] = { + '#', '!', 'A', 'M', 'R', '-', 'W', 'B', '\n' +}; + +static guint8 garbage_frame[] = { + 0xff, 0xff, 0xff, 0xff, 0xff +}; + + +GST_START_TEST (test_parse_nb_normal) +{ + gst_parser_test_normal (frame_data_nb, sizeof (frame_data_nb)); +} + +GST_END_TEST; + + +GST_START_TEST (test_parse_nb_drain_single) +{ + gst_parser_test_drain_single (frame_data_nb, sizeof (frame_data_nb)); +} + +GST_END_TEST; + + +GST_START_TEST (test_parse_nb_drain_garbage) +{ + gst_parser_test_drain_garbage (frame_data_nb, sizeof (frame_data_nb), + garbage_frame, sizeof (garbage_frame)); +} + +GST_END_TEST; + + +GST_START_TEST (test_parse_nb_split) +{ + gst_parser_test_split (frame_data_nb, sizeof (frame_data_nb)); +} + +GST_END_TEST; + + +GST_START_TEST (test_parse_nb_skip_garbage) +{ + gst_parser_test_skip_garbage (frame_data_nb, sizeof (frame_data_nb), + garbage_frame, sizeof (garbage_frame)); +} + +GST_END_TEST; + + +GST_START_TEST (test_parse_nb_detect_stream) +{ + GstParserTest ptest; + GstCaps *old_ctx_caps; + + /* no input caps, override ctx */ + old_ctx_caps = ctx_input_caps; + ctx_input_caps = NULL; + + /* AMR-NB header */ + gst_parser_test_init (&ptest, frame_hdr_nb, sizeof (frame_hdr_nb), 1); + /* well, no garbage, followed by real data */ + ptest.series[2].data = frame_data_nb; + ptest.series[2].size = sizeof (frame_data_nb); + ptest.series[2].num = 10; + /* header gets dropped, so ... */ + /* buffer count will not match */ + ptest.framed = FALSE; + /* total size a bit less */ + ptest.dropped = sizeof (frame_hdr_nb); + + /* Check that the negotiated caps are as expected */ + ptest.sink_caps = gst_caps_from_string (SINK_CAPS_NB); + + gst_parser_test_run (&ptest, NULL); + + gst_caps_unref (ptest.sink_caps); + + ctx_input_caps = old_ctx_caps; +} + +GST_END_TEST; + + +GST_START_TEST (test_parse_wb_normal) +{ + gst_parser_test_normal (frame_data_wb, sizeof (frame_data_wb)); +} + +GST_END_TEST; + + +GST_START_TEST (test_parse_wb_drain_single) +{ + gst_parser_test_drain_single (frame_data_wb, sizeof (frame_data_wb)); +} + +GST_END_TEST; + + +GST_START_TEST (test_parse_wb_drain_garbage) +{ + gst_parser_test_drain_garbage (frame_data_wb, sizeof (frame_data_wb), + garbage_frame, sizeof (garbage_frame)); +} + +GST_END_TEST; + + +GST_START_TEST (test_parse_wb_split) +{ + gst_parser_test_split (frame_data_wb, sizeof (frame_data_wb)); +} + +GST_END_TEST; + + +GST_START_TEST (test_parse_wb_skip_garbage) +{ + gst_parser_test_skip_garbage (frame_data_wb, sizeof (frame_data_wb), + garbage_frame, sizeof (garbage_frame)); +} + +GST_END_TEST; + + +GST_START_TEST (test_parse_wb_detect_stream) +{ + GstParserTest ptest; + GstCaps *old_ctx_caps; + + /* no input caps, override ctx */ + old_ctx_caps = ctx_input_caps; + ctx_input_caps = NULL; + + /* AMR-WB header */ + gst_parser_test_init (&ptest, frame_hdr_wb, sizeof (frame_hdr_wb), 1); + /* well, no garbage, followed by real data */ + ptest.series[2].data = frame_data_wb; + ptest.series[2].size = sizeof (frame_data_wb); + ptest.series[2].num = 10; + /* header gets dropped, so ... */ + /* buffer count will not match */ + ptest.framed = FALSE; + /* total size a bit less */ + ptest.dropped = sizeof (frame_hdr_wb); + + /* Check that the negotiated caps are as expected */ + ptest.sink_caps = gst_caps_from_string (SINK_CAPS_WB); + + gst_parser_test_run (&ptest, NULL); + + gst_caps_unref (ptest.sink_caps); + + ctx_input_caps = old_ctx_caps; +} + +GST_END_TEST; + + + +/* + * Create test suite. + */ +static Suite * +amrnb_parse_suite (void) +{ + Suite *s = suite_create ("amrwb_parse"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + /* AMR-NB tests */ + tcase_add_test (tc_chain, test_parse_nb_normal); + tcase_add_test (tc_chain, test_parse_nb_drain_single); + tcase_add_test (tc_chain, test_parse_nb_drain_garbage); + tcase_add_test (tc_chain, test_parse_nb_split); + tcase_add_test (tc_chain, test_parse_nb_detect_stream); + tcase_add_test (tc_chain, test_parse_nb_skip_garbage); + + return s; +} + +static Suite * +amrwb_parse_suite (void) +{ + Suite *s = suite_create ("amrnb_parse"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + /* AMR-WB tests */ + tcase_add_test (tc_chain, test_parse_wb_normal); + tcase_add_test (tc_chain, test_parse_wb_drain_single); + tcase_add_test (tc_chain, test_parse_wb_drain_garbage); + tcase_add_test (tc_chain, test_parse_wb_split); + tcase_add_test (tc_chain, test_parse_wb_detect_stream); + tcase_add_test (tc_chain, test_parse_wb_skip_garbage); + + return s; +} + +/* + * TODO: + * - Both push- and pull-modes need to be tested + * * Pull-mode & EOS + */ + +int +main (int argc, char **argv) +{ + int nf; + GstCaps *caps; + + Suite *s = amrnb_parse_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + /* init test context */ + ctx_factory = "amrparse"; + ctx_sink_template = &sinktemplate_nb; + ctx_src_template = &srctemplate_nb; + caps = gst_caps_from_string (SRC_CAPS_NB); + g_assert (caps); + ctx_input_caps = caps; + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + gst_caps_unref (caps); + + s = amrwb_parse_suite (); + sr = srunner_create (s); + + ctx_sink_template = &sinktemplate_wb; + ctx_src_template = &srctemplate_wb; + caps = gst_caps_from_string (SRC_CAPS_WB); + g_assert (caps); + ctx_input_caps = caps; + + srunner_run_all (sr, CK_NORMAL); + nf += srunner_ntests_failed (sr); + srunner_free (sr); + gst_caps_unref (caps); + + return nf; +} diff --git a/tests/check/elements/apev2mux.c b/tests/check/elements/apev2mux.c new file mode 100644 index 0000000..88f9e3e --- /dev/null +++ b/tests/check/elements/apev2mux.c @@ -0,0 +1,426 @@ +/* GStreamer + * + * unit test for the taglib-based apev2mux element + * + * Copyright (C) 2006 Tim-Philipp Müller <tim centricular net> + * Copyright (C) 2006 Sebastian Dröge <slomo@circular-chaos.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. + */ + +#include <gst/check/gstcheck.h> + +#include <gst/gst.h> +#include <string.h> + +#define TEST_ARTIST "Ar T\303\255st" +#define TEST_TITLE "M\303\274llermilch!" +#define TEST_ALBUM "Boom" +#define TEST_DATE g_date_new_dmy(1,1,2006) +#define TEST_TRACK_NUMBER 7 +#define TEST_TRACK_COUNT 19 +#define TEST_TRACK_GAIN 1.45 +#define TEST_ALBUM_GAIN 0.78 + +/* for dummy mp3 frame sized MP3_FRAME_SIZE bytes, + * start: ff fb b0 44 00 00 08 00 00 4b 00 00 00 00 00 00 */ +static const guint8 mp3_dummyhdr[] = { 0xff, 0xfb, 0xb0, 0x44, 0x00, 0x00, + 0x08, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00 +}; + +#define MP3_FRAME_SIZE 626 + +static GstTagList * +test_taglib_apev2mux_create_tags (guint32 mask) +{ + GstTagList *tags; + + tags = gst_tag_list_new_empty (); + + if (mask & (1 << 0)) { + gst_tag_list_add (tags, GST_TAG_MERGE_KEEP, + GST_TAG_ARTIST, TEST_ARTIST, NULL); + } + if (mask & (1 << 1)) { + gst_tag_list_add (tags, GST_TAG_MERGE_KEEP, + GST_TAG_TITLE, TEST_TITLE, NULL); + } + if (mask & (1 << 2)) { + gst_tag_list_add (tags, GST_TAG_MERGE_KEEP, + GST_TAG_ALBUM, TEST_ALBUM, NULL); + } + if (mask & (1 << 3)) { + GDate *date; + + date = TEST_DATE; + gst_tag_list_add (tags, GST_TAG_MERGE_KEEP, GST_TAG_DATE, date, NULL); + g_date_free (date); + } + if (mask & (1 << 4)) { + gst_tag_list_add (tags, GST_TAG_MERGE_KEEP, + GST_TAG_TRACK_NUMBER, TEST_TRACK_NUMBER, NULL); + } + if (mask & (1 << 5)) { + gst_tag_list_add (tags, GST_TAG_MERGE_KEEP, + GST_TAG_TRACK_COUNT, TEST_TRACK_COUNT, NULL); + } + if (mask & (1 << 6)) { + gst_tag_list_add (tags, GST_TAG_MERGE_KEEP, + GST_TAG_TRACK_GAIN, TEST_TRACK_GAIN, NULL); + } + if (mask & (1 << 7)) { + gst_tag_list_add (tags, GST_TAG_MERGE_KEEP, + GST_TAG_ALBUM_GAIN, TEST_ALBUM_GAIN, NULL); + } + if (mask & (1 << 8)) { + } + if (mask & (1 << 9)) { + } + if (mask & (1 << 10)) { + } + if (mask & (1 << 11)) { + } + if (mask & (1 << 12)) { + } + if (mask & (1 << 13)) { + } + return tags; +} + +static void +test_taglib_apev2mux_check_tags (GstTagList * tags, guint32 mask) +{ + if (mask & (1 << 0)) { + gchar *s = NULL; + + fail_unless (gst_tag_list_get_string (tags, GST_TAG_ARTIST, &s)); + fail_unless (g_str_equal (s, TEST_ARTIST)); + g_free (s); + } + if (mask & (1 << 1)) { + gchar *s = NULL; + + fail_unless (gst_tag_list_get_string (tags, GST_TAG_TITLE, &s)); + fail_unless (g_str_equal (s, TEST_TITLE)); + g_free (s); + } + if (mask & (1 << 2)) { + gchar *s = NULL; + + fail_unless (gst_tag_list_get_string (tags, GST_TAG_ALBUM, &s)); + fail_unless (g_str_equal (s, TEST_ALBUM)); + g_free (s); + } + if (mask & (1 << 3)) { + GDate *shouldbe, *date = NULL; + + shouldbe = TEST_DATE; + fail_unless (gst_tag_list_get_date (tags, GST_TAG_DATE, &date)); + fail_unless (g_date_compare (shouldbe, date) == 0); + g_date_free (shouldbe); + g_date_free (date); + } + if (mask & (1 << 4)) { + guint num; + + fail_unless (gst_tag_list_get_uint (tags, GST_TAG_TRACK_NUMBER, &num)); + fail_unless (num == TEST_TRACK_NUMBER); + } + if (mask & (1 << 5)) { + guint count; + + fail_unless (gst_tag_list_get_uint (tags, GST_TAG_TRACK_COUNT, &count)); + fail_unless (count == TEST_TRACK_COUNT); + } + if (mask & (1 << 6)) { + gdouble gain; + + fail_unless (gst_tag_list_get_double (tags, GST_TAG_TRACK_GAIN, &gain)); + fail_unless (gain == TEST_TRACK_GAIN); + } + if (mask & (1 << 7)) { + gdouble gain; + + fail_unless (gst_tag_list_get_double (tags, GST_TAG_ALBUM_GAIN, &gain)); + fail_unless (gain == TEST_ALBUM_GAIN); + } + if (mask & (1 << 8)) { + } + if (mask & (1 << 9)) { + } + if (mask & (1 << 10)) { + } + if (mask & (1 << 11)) { + } + if (mask & (1 << 12)) { + } + if (mask & (1 << 13)) { + } +} + +static void +fill_mp3_buffer (GstElement * fakesrc, GstBuffer * buf, GstPad * pad, + guint64 * p_offset) +{ + gsize size; + + size = gst_buffer_get_size (buf); + + fail_unless (size == MP3_FRAME_SIZE); + + GST_LOG ("filling buffer with fake mp3 data, offset = %" G_GUINT64_FORMAT, + *p_offset); + + gst_buffer_fill (buf, 0, mp3_dummyhdr, sizeof (mp3_dummyhdr)); + +#if 0 + /* can't use gst_buffer_set_caps() here because the metadata isn't writable + * because of the extra refcounts taken by the signal emission mechanism; + * we know it's fine to use GST_BUFFER_CAPS() here though */ + GST_BUFFER_CAPS (buf) = gst_caps_new_simple ("audio/mpeg", "mpegversion", + G_TYPE_INT, 1, "layer", G_TYPE_INT, 3, NULL); +#endif + + GST_BUFFER_OFFSET (buf) = *p_offset; + *p_offset += size; +} + +static void +got_buffer (GstElement * fakesink, GstBuffer * buf, GstPad * pad, + GstBuffer ** p_buf) +{ + gint64 off; + GstMapInfo map; + + off = GST_BUFFER_OFFSET (buf); + + gst_buffer_map (buf, &map, GST_MAP_READ); + + GST_LOG ("size=%" G_GSIZE_FORMAT ", offset=%" G_GINT64_FORMAT, map.size, off); + + fail_unless (GST_BUFFER_OFFSET_IS_VALID (buf)); + + if (*p_buf == NULL || (off + map.size) > gst_buffer_get_size (*p_buf)) { + GstBuffer *newbuf; + + /* not very elegant, but who cares */ + newbuf = gst_buffer_new_and_alloc (off + map.size); + if (*p_buf) { + GstMapInfo pmap; + + gst_buffer_map (*p_buf, &pmap, GST_MAP_READ); + gst_buffer_fill (newbuf, 0, pmap.data, pmap.size); + gst_buffer_unmap (*p_buf, &pmap); + } + gst_buffer_fill (newbuf, off, map.data, map.size); + if (*p_buf) + gst_buffer_unref (*p_buf); + *p_buf = newbuf; + } else { + gst_buffer_fill (*p_buf, off, map.data, map.size); + } + gst_buffer_unmap (buf, &map); +} + +static void +test_taglib_apev2mux_check_output_buffer (GstBuffer * buf) +{ + GstMapInfo map; + guint off; + + gst_buffer_map (buf, &map, GST_MAP_READ); + g_assert (map.size % MP3_FRAME_SIZE == 0); + + for (off = 0; off < map.size; off += MP3_FRAME_SIZE) { + fail_unless (memcmp (map.data + off, mp3_dummyhdr, + sizeof (mp3_dummyhdr)) == 0); + } + gst_buffer_unmap (buf, &map); +} + +static void +test_taglib_apev2mux_with_tags (GstTagList * tags, guint32 mask) +{ + GstMessage *msg; + GstTagList *tags_read = NULL; + GstElement *pipeline, *apev2mux, *apedemux, *fakesrc, *fakesink; + GstBus *bus; + guint64 offset; + GstBuffer *outbuf = NULL; + + pipeline = gst_pipeline_new ("pipeline"); + g_assert (pipeline != NULL); + + fakesrc = gst_element_factory_make ("fakesrc", "fakesrc"); + g_assert (fakesrc != NULL); + + apev2mux = gst_element_factory_make ("apev2mux", "apev2mux"); + g_assert (apev2mux != NULL); + + apedemux = gst_element_factory_make ("apedemux", "apedemux"); + g_assert (apedemux != NULL); + + fakesink = gst_element_factory_make ("fakesink", "fakesink"); + g_assert (fakesink != NULL); + + /* set up sink */ + outbuf = NULL; + g_object_set (fakesink, "signal-handoffs", TRUE, NULL); + g_signal_connect (fakesink, "handoff", G_CALLBACK (got_buffer), &outbuf); + + gst_bin_add (GST_BIN (pipeline), fakesrc); + gst_bin_add (GST_BIN (pipeline), apev2mux); + gst_bin_add (GST_BIN (pipeline), apedemux); + gst_bin_add (GST_BIN (pipeline), fakesink); + + gst_tag_setter_merge_tags (GST_TAG_SETTER (apev2mux), tags, + GST_TAG_MERGE_APPEND); + + gst_element_link_many (fakesrc, apev2mux, apedemux, fakesink, NULL); + + /* set up source */ + g_object_set (fakesrc, "signal-handoffs", TRUE, "can-activate-pull", FALSE, + "filltype", 2, "sizetype", 2, "sizemax", MP3_FRAME_SIZE, + "num-buffers", 16, NULL); + + offset = 0; + g_signal_connect (fakesrc, "handoff", G_CALLBACK (fill_mp3_buffer), &offset); + + gst_element_set_state (pipeline, GST_STATE_PLAYING); + fail_unless (gst_element_get_state (pipeline, NULL, NULL, + -1) == GST_STATE_CHANGE_SUCCESS); + + bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); + + GST_LOG ("Waiting for tag ..."); + msg = + gst_bus_poll (bus, GST_MESSAGE_TAG | GST_MESSAGE_EOS | GST_MESSAGE_ERROR, + -1); + if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) { + GError *err; + gchar *dbg; + + gst_message_parse_error (msg, &err, &dbg); + g_printerr ("ERROR from element %s: %s\n%s\n", + GST_OBJECT_NAME (msg->src), err->message, GST_STR_NULL (dbg)); + g_error_free (err); + g_free (dbg); + } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_EOS) { + g_printerr ("EOS message, but were waiting for TAGS!\n"); + } + fail_unless (msg->type == GST_MESSAGE_TAG); + + gst_message_parse_tag (msg, &tags_read); + gst_message_unref (msg); + + GST_LOG ("Got tags: %" GST_PTR_FORMAT, tags_read); + test_taglib_apev2mux_check_tags (tags_read, mask); + gst_tag_list_unref (tags_read); + + GST_LOG ("Waiting for EOS ..."); + msg = gst_bus_poll (bus, GST_MESSAGE_EOS | GST_MESSAGE_ERROR, -1); + if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) { + GError *err; + gchar *dbg; + + gst_message_parse_error (msg, &err, &dbg); + g_printerr ("ERROR from element %s: %s\n%s\n", + GST_OBJECT_NAME (msg->src), err->message, GST_STR_NULL (dbg)); + g_error_free (err); + g_free (dbg); + } + fail_unless (msg->type == GST_MESSAGE_EOS); + gst_message_unref (msg); + + gst_object_unref (bus); + + GST_LOG ("Got EOS, shutting down ..."); + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_object_unref (pipeline); + + test_taglib_apev2mux_check_output_buffer (outbuf); + gst_buffer_unref (outbuf); + + GST_LOG ("Done"); +} + +GST_START_TEST (test_apev2mux) +{ + GstTagList *tags; + gint i; + + g_random_set_seed (247166295); + + /* internal consistency check */ + tags = test_taglib_apev2mux_create_tags (0xFFFFFFFF); + test_taglib_apev2mux_check_tags (tags, 0xFFFFFFFF); + gst_tag_list_unref (tags); + + /* now the real tests */ + for (i = 0; i < 50; ++i) { + guint32 mask; + + mask = g_random_int (); + GST_LOG ("tag mask = %08x (i=%d)", mask, i); + + if (mask == 0) + continue; + + /* create tags */ + tags = test_taglib_apev2mux_create_tags (mask); + GST_LOG ("tags for mask %08x = %" GST_PTR_FORMAT, mask, tags); + + /* double-check for internal consistency */ + test_taglib_apev2mux_check_tags (tags, mask); + + /* test with pipeline */ + test_taglib_apev2mux_with_tags (tags, mask); + + /* free tags */ + gst_tag_list_unref (tags); + } +} + +GST_END_TEST; + +static Suite * +apev2mux_suite (void) +{ + Suite *s = suite_create ("apev2mux"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_apev2mux); + + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = apev2mux_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/aspectratiocrop.c b/tests/check/elements/aspectratiocrop.c new file mode 100644 index 0000000..1728afd --- /dev/null +++ b/tests/check/elements/aspectratiocrop.c @@ -0,0 +1,190 @@ +/* GStreamer unit test for the aspectratiocrop element + * Copyright (C) 2009 Thijs Vermeir <thijsvermeir@gmail.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 + +#include <gst/gst.h> +#include <gst/video/video.h> +#include <gst/check/gstcheck.h> + +#define ASPECT_RATIO_CROP_CAPS \ + GST_VIDEO_CAPS_MAKE ("{ RGBx, xRGB, BGRx, xBGR, " \ + "RGBA, ARGB, BGRA, ABGR, RGB, BGR, AYUV, " \ + "YUY2, YVYU, UYVY, GRAY8, I420, YV12, RGB16, " \ + "RGB15 }") + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (ASPECT_RATIO_CROP_CAPS) + ); + +static void +check_aspectratiocrop (const gchar * in_string, const gchar * out_string, + gint in_size, gint out_size, gint ar_n, gint ar_d) +{ + GstElement *element; + GstPad *pad_peer; + GstPad *sink_pad = NULL; + GstPad *src_pad; + GstBuffer *new; + GstBuffer *buffer; + GstBuffer *buffer_out; + GstCaps *incaps; + GstCaps *outcaps; + + incaps = gst_caps_from_string (in_string); + buffer = gst_buffer_new_and_alloc (in_size); + outcaps = gst_caps_from_string (out_string); + buffer_out = gst_buffer_new_and_alloc (out_size); + + /* check that there are no buffers waiting */ + gst_check_drop_buffers (); + + /* create the element */ + element = gst_check_setup_element ("aspectratiocrop"); + + /* set the requested aspect ratio */ + g_object_set (G_OBJECT (element), "aspect-ratio", ar_n, ar_d, NULL); + + /* create the src pad */ + src_pad = gst_pad_new (NULL, GST_PAD_SRC); + gst_pad_set_active (src_pad, TRUE); + GST_DEBUG ("setting caps %s %" GST_PTR_FORMAT, in_string, incaps); + gst_check_setup_events (src_pad, element, incaps, GST_FORMAT_TIME); + + pad_peer = gst_element_get_static_pad (element, "sink"); + fail_if (pad_peer == NULL); + fail_unless (gst_pad_link (src_pad, pad_peer) == GST_PAD_LINK_OK, + "Could not link source and %s sink pads", GST_ELEMENT_NAME (element)); + gst_object_unref (pad_peer); + + /* create the sink pad */ + pad_peer = gst_element_get_static_pad (element, "src"); + sink_pad = gst_pad_new_from_static_template (&sinktemplate, "sink"); + fail_unless (gst_pad_link (pad_peer, sink_pad) == GST_PAD_LINK_OK, + "Could not link sink and %s source pads", GST_ELEMENT_NAME (element)); + gst_object_unref (pad_peer); + gst_pad_set_chain_function (sink_pad, gst_check_chain_func); + gst_pad_set_active (sink_pad, TRUE); + + /* configure the sink pad */ + fail_unless (gst_element_set_state (element, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + fail_unless (gst_pad_push (src_pad, buffer) == GST_FLOW_OK, + "Failed to push buffer"); + fail_unless (gst_element_set_state (element, + GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS, "could not set to null"); + + /* check the result */ + fail_unless (g_list_length (buffers) == 1); + new = GST_BUFFER (buffers->data); + buffers = g_list_remove (buffers, new); + fail_unless (gst_buffer_get_size (buffer_out) == gst_buffer_get_size (new), + "size of the buffers are not the same"); + { + GstCaps *sinkpad_caps; + + sinkpad_caps = gst_pad_get_current_caps (sink_pad); + + gst_check_caps_equal (sinkpad_caps, outcaps); + + gst_caps_unref (sinkpad_caps); + } + gst_buffer_unref (new); + gst_buffer_unref (buffer_out); + gst_caps_unref (outcaps); + gst_caps_unref (incaps); + + /* teardown the element and pads */ + gst_pad_set_active (src_pad, FALSE); + gst_check_teardown_src_pad (element); + gst_pad_set_active (sink_pad, FALSE); + gst_check_teardown_sink_pad (element); + gst_check_teardown_element (element); +} + +GST_START_TEST (test_no_cropping) +{ + check_aspectratiocrop + ("video/x-raw, format=(string)YUY2, width=(int)320, height=(int)240, framerate=(fraction)30/1", + "video/x-raw, format=(string)YUY2, width=(int)320, height=(int)240, framerate=(fraction)30/1", + 153600, 153600, 4, 3); + check_aspectratiocrop + ("video/x-raw, format=(string)YUY2, width=(int)320, height=(int)320, framerate=(fraction)30/1, pixel-aspect-ratio=(fraction)4/3", + "video/x-raw, format=(string)YUY2, width=(int)320, height=(int)320, framerate=(fraction)30/1, pixel-aspect-ratio=(fraction)4/3", + 204800, 204800, 4, 3); +} + +GST_END_TEST; + +GST_START_TEST (test_autocropping) +{ + check_aspectratiocrop + ("video/x-raw, format=(string)YUY2, width=(int)320, height=(int)240, framerate=(fraction)30/1, pixel-aspect-ratio=(fraction)4/3", + "video/x-raw, format=(string)YUY2, width=(int)240, height=(int)240, framerate=(fraction)30/1, pixel-aspect-ratio=(fraction)4/3", + 153600, 115200, 4, 3); + + check_aspectratiocrop + ("video/x-raw, format=(string)YUY2, width=(int)320, height=(int)240, framerate=(fraction)30/1, pixel-aspect-ratio=(fraction)16/9", + "video/x-raw, format=(string)YUY2, width=(int)180, height=(int)240, framerate=(fraction)30/1, pixel-aspect-ratio=(fraction)16/9", + 153600, 86400, 4, 3); + + check_aspectratiocrop + ("video/x-raw, format=(string)YUY2, width=(int)320, height=(int)240, framerate=(fraction)30/1, pixel-aspect-ratio=(fraction)16/15", + "video/x-raw, format=(string)YUY2, width=(int)320, height=(int)192, framerate=(fraction)30/1, pixel-aspect-ratio=(fraction)16/15", + 153600, 122880, 16, 9); + +} + +GST_END_TEST; + +static Suite * +aspectratiocrop_suite (void) +{ + Suite *s = suite_create ("aspectratiocrop"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_no_cropping); + tcase_add_test (tc_chain, test_autocropping); + + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = aspectratiocrop_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/audioamplify.c b/tests/check/elements/audioamplify.c new file mode 100644 index 0000000..bebb004 --- /dev/null +++ b/tests/check/elements/audioamplify.c @@ -0,0 +1,482 @@ +/* GStreamer + * + * unit test for audioamplify + * + * Copyright (C) 2007 Sebastian Dröge <slomo@circular-chaos.org> + * + * Greatly based on the audiopanorama unit test + * Copyright (C) 2006 Stefan Kost <ensonic@users.sf.net> + * + * 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 <unistd.h> + +#include <gst/base/gstbasetransform.h> +#include <gst/check/gstcheck.h> +#include <gst/audio/audio.h> + +gboolean have_eos = FALSE; + +/* 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 */ +GstPad *mysrcpad, *mysinkpad; + + +#define AMPLIFY_CAPS_STRING \ + "audio/x-raw, " \ + "channels = (int) 1, " \ + "rate = (int) 44100, " \ + "layout = (string) interleaved, " \ + "format = (string) " GST_AUDIO_NE(S16) + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "channels = (int) 1, " + "rate = (int) [ 1, MAX ], " + "layout = (string) interleaved, " + "format = (string) " GST_AUDIO_NE (S16))); +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "channels = (int) 1, " + "rate = (int) [ 1, MAX ], " + "layout = (string) interleaved, " + "format = (string) " GST_AUDIO_NE (S16))); + +static GstElement * +setup_amplify (void) +{ + GstElement *amplify; + + GST_DEBUG ("setup_amplify"); + amplify = gst_check_setup_element ("audioamplify"); + mysrcpad = gst_check_setup_src_pad (amplify, &srctemplate); + mysinkpad = gst_check_setup_sink_pad (amplify, &sinktemplate); + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + + return amplify; +} + +static void +cleanup_amplify (GstElement * amplify) +{ + GST_DEBUG ("cleanup_amplify"); + + g_list_foreach (buffers, (GFunc) gst_mini_object_unref, NULL); + g_list_free (buffers); + buffers = NULL; + + gst_pad_set_active (mysrcpad, FALSE); + gst_pad_set_active (mysinkpad, FALSE); + gst_check_teardown_src_pad (amplify); + gst_check_teardown_sink_pad (amplify); + gst_check_teardown_element (amplify); +} + +GST_START_TEST (test_passthrough) +{ + GstElement *amplify; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gint16 in[6] = { 24576, -16384, 256, -128, 0, -24576 }; + gint16 res[6]; + + amplify = setup_amplify (); + fail_unless (gst_element_set_state (amplify, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + caps = gst_caps_from_string (AMPLIFY_CAPS_STRING); + gst_check_setup_events (mysrcpad, amplify, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + + inbuffer = + gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY, in, 12, 0, 12, + NULL, NULL); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 12) == 0); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being collected on the global buffer list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + fail_unless (gst_buffer_extract (outbuffer, 0, res, 12) == 12); + GST_INFO + ("expected %+5d %+5d %+5d %+5d %+5d %+5d real %+5d %+5d %+5d %+5d %+5d %+5d", + in[0], in[1], in[2], in[3], in[4], in[5], res[0], res[1], res[2], res[3], + res[4], res[5]); + fail_unless (gst_buffer_memcmp (outbuffer, 0, in, 12) == 0); + + /* cleanup */ + cleanup_amplify (amplify); +} + +GST_END_TEST; + +GST_START_TEST (test_zero) +{ + GstElement *amplify; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gint16 in[6] = { 24576, -16384, 256, -128, 0, -24576 }; + gint16 out[6] = { 0, 0, 0, 0, 0, 0 }; + gint16 res[6]; + + amplify = setup_amplify (); + g_object_set (G_OBJECT (amplify), "amplification", 0.0, NULL); + fail_unless (gst_element_set_state (amplify, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + caps = gst_caps_from_string (AMPLIFY_CAPS_STRING); + gst_check_setup_events (mysrcpad, amplify, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + + inbuffer = + gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY, in, 12, 0, 12, + NULL, NULL); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 12) == 0); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + fail_unless (gst_buffer_extract (outbuffer, 0, res, 12) == 12); + GST_INFO + ("expected %+5d %+5d %+5d %+5d %+5d %+5d real %+5d %+5d %+5d %+5d %+5d %+5d", + out[0], out[1], out[2], out[3], out[4], out[5], res[0], res[1], res[2], + res[3], res[4], res[5]); + fail_unless (gst_buffer_memcmp (outbuffer, 0, out, 12) == 0); + + /* cleanup */ + cleanup_amplify (amplify); +} + +GST_END_TEST; + +GST_START_TEST (test_050_clip) +{ + GstElement *amplify; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gint16 in[6] = { 24576, -16384, 256, -128, 0, -24576 }; + gint16 out[6] = { 12288, -8192, 128, -64, 0, -12288 }; + gint16 res[6]; + + amplify = setup_amplify (); + g_object_set (G_OBJECT (amplify), "amplification", 0.5, NULL); + fail_unless (gst_element_set_state (amplify, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + caps = gst_caps_from_string (AMPLIFY_CAPS_STRING); + gst_check_setup_events (mysrcpad, amplify, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + + inbuffer = + gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY, in, 12, 0, 12, + NULL, NULL); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 12) == 0); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + fail_unless (gst_buffer_extract (outbuffer, 0, res, 12) == 12); + GST_INFO + ("expected %+5d %+5d %+5d %+5d %+5d %+5d real %+5d %+5d %+5d %+5d %+5d %+5d", + out[0], out[1], out[2], out[3], out[4], out[5], res[0], res[1], res[2], + res[3], res[4], res[5]); + fail_unless (gst_buffer_memcmp (outbuffer, 0, out, 12) == 0); + + /* cleanup */ + cleanup_amplify (amplify); +} + +GST_END_TEST; + +GST_START_TEST (test_200_clip) +{ + GstElement *amplify; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gint16 in[6] = { 24576, -16384, 256, -128, 0, -24576 }; + gint16 out[6] = { G_MAXINT16, -32768, 512, -256, 0, G_MININT16 }; + gint16 res[6]; + + amplify = setup_amplify (); + g_object_set (G_OBJECT (amplify), "amplification", 2.0, NULL); + fail_unless (gst_element_set_state (amplify, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + caps = gst_caps_from_string (AMPLIFY_CAPS_STRING); + gst_check_setup_events (mysrcpad, amplify, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + + inbuffer = + gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY, in, 12, 0, 12, + NULL, NULL); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 12) == 0); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + fail_unless (gst_buffer_extract (outbuffer, 0, res, 12) == 12); + GST_INFO + ("expected %+5d %+5d %+5d %+5d %+5d %+5d real %+5d %+5d %+5d %+5d %+5d %+5d", + out[0], out[1], out[2], out[3], out[4], out[5], res[0], res[1], res[2], + res[3], res[4], res[5]); + fail_unless (gst_buffer_memcmp (outbuffer, 0, out, 12) == 0); + + /* cleanup */ + cleanup_amplify (amplify); +} + +GST_END_TEST; + +GST_START_TEST (test_050_wrap_negative) +{ + GstElement *amplify; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gint16 in[6] = { 24576, -16384, 256, -128, 0, -24576 }; + gint16 out[6] = { 12288, -8192, 128, -64, 0, -12288 }; + gint16 res[6]; + + amplify = setup_amplify (); + g_object_set (G_OBJECT (amplify), "amplification", 0.5, NULL); + g_object_set (G_OBJECT (amplify), "clipping-method", 1, NULL); + fail_unless (gst_element_set_state (amplify, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + caps = gst_caps_from_string (AMPLIFY_CAPS_STRING); + gst_check_setup_events (mysrcpad, amplify, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + + inbuffer = + gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY, in, 12, 0, 12, + NULL, NULL); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 12) == 0); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + fail_unless (gst_buffer_extract (outbuffer, 0, res, 12) == 12); + GST_INFO + ("expected %+5d %+5d %+5d %+5d %+5d %+5d real %+5d %+5d %+5d %+5d %+5d %+5d", + out[0], out[1], out[2], out[3], out[4], out[5], res[0], res[1], res[2], + res[3], res[4], res[5]); + fail_unless (gst_buffer_memcmp (outbuffer, 0, out, 12) == 0); + + /* cleanup */ + cleanup_amplify (amplify); +} + +GST_END_TEST; + +GST_START_TEST (test_200_wrap_negative) +{ + GstElement *amplify; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gint16 in[6] = { 24576, -16384, 256, -128, 0, -24576 }; + gint16 out[6] = { -16384, -32768, 512, -256, 0, 16384 }; + gint16 res[6]; + + amplify = setup_amplify (); + g_object_set (G_OBJECT (amplify), "amplification", 2.0, NULL); + g_object_set (G_OBJECT (amplify), "clipping-method", 1, NULL); + fail_unless (gst_element_set_state (amplify, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + caps = gst_caps_from_string (AMPLIFY_CAPS_STRING); + gst_check_setup_events (mysrcpad, amplify, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + + inbuffer = + gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY, in, 12, 0, 12, + NULL, NULL); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 12) == 0); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + fail_unless (gst_buffer_extract (outbuffer, 0, res, 12) == 12); + GST_INFO + ("expected %+5d %+5d %+5d %+5d %+5d %+5d real %+5d %+5d %+5d %+5d %+5d %+5d", + out[0], out[1], out[2], out[3], out[4], out[5], res[0], res[1], res[2], + res[3], res[4], res[5]); + fail_unless (gst_buffer_memcmp (outbuffer, 0, out, 12) == 0); + + /* cleanup */ + cleanup_amplify (amplify); +} + +GST_END_TEST; + +GST_START_TEST (test_050_wrap_positive) +{ + GstElement *amplify; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gint16 in[6] = { 24576, -16384, 256, -128, 0, -24576 }; + gint16 out[6] = { 12288, -8192, 128, -64, 0, -12288 }; + gint16 res[6]; + + amplify = setup_amplify (); + g_object_set (G_OBJECT (amplify), "amplification", 0.5, NULL); + g_object_set (G_OBJECT (amplify), "clipping-method", 2, NULL); + fail_unless (gst_element_set_state (amplify, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + caps = gst_caps_from_string (AMPLIFY_CAPS_STRING); + gst_check_setup_events (mysrcpad, amplify, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + + inbuffer = + gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY, in, 12, 0, 12, + NULL, NULL); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 12) == 0); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + fail_unless (gst_buffer_extract (outbuffer, 0, res, 12) == 12); + GST_INFO + ("expected %+5d %+5d %+5d %+5d %+5d %+5d real %+5d %+5d %+5d %+5d %+5d %+5d", + out[0], out[1], out[2], out[3], out[4], out[5], res[0], res[1], res[2], + res[3], res[4], res[5]); + fail_unless (gst_buffer_memcmp (outbuffer, 0, out, 12) == 0); + + /* cleanup */ + cleanup_amplify (amplify); +} + +GST_END_TEST; + +GST_START_TEST (test_200_wrap_positive) +{ + GstElement *amplify; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gint16 in[6] = { 24576, -16384, 256, -128, 0, -24576 }; + gint16 out[6] = { 16382, -32768, 512, -256, 0, -16384 }; + gint16 res[6]; + + amplify = setup_amplify (); + g_object_set (G_OBJECT (amplify), "amplification", 2.0, NULL); + g_object_set (G_OBJECT (amplify), "clipping-method", 2, NULL); + fail_unless (gst_element_set_state (amplify, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + caps = gst_caps_from_string (AMPLIFY_CAPS_STRING); + gst_check_setup_events (mysrcpad, amplify, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + + inbuffer = + gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY, in, 12, 0, 12, + NULL, NULL); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 12) == 0); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + fail_unless (gst_buffer_extract (outbuffer, 0, res, 12) == 12); + GST_INFO + ("expected %+5d %+5d %+5d %+5d %+5d %+5d real %+5d %+5d %+5d %+5d %+5d %+5d", + out[0], out[1], out[2], out[3], out[4], out[5], res[0], res[1], res[2], + res[3], res[4], res[5]); + fail_unless (gst_buffer_memcmp (outbuffer, 0, out, 12) == 0); + + /* cleanup */ + cleanup_amplify (amplify); +} + +GST_END_TEST; + +static Suite * +amplify_suite (void) +{ + Suite *s = suite_create ("amplify"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_passthrough); + tcase_add_test (tc_chain, test_zero); + tcase_add_test (tc_chain, test_050_clip); + tcase_add_test (tc_chain, test_200_clip); + tcase_add_test (tc_chain, test_050_wrap_negative); + tcase_add_test (tc_chain, test_200_wrap_negative); + tcase_add_test (tc_chain, test_050_wrap_positive); + tcase_add_test (tc_chain, test_200_wrap_positive); + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = amplify_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/audiochebband.c b/tests/check/elements/audiochebband.c new file mode 100644 index 0000000..7021822 --- /dev/null +++ b/tests/check/elements/audiochebband.c @@ -0,0 +1,1685 @@ +/* GStreamer + * + * Copyright (C) 2007 Sebastian Dröge <slomo@circular-chaos.org> + * + * audiochebband.c: Unit test for the audiochebband element + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <gst/gst.h> +#include <gst/base/gstbasetransform.h> +#include <gst/audio/audio.h> +#include <gst/check/gstcheck.h> + +#include <math.h> + +/* 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 */ +GstPad *mysrcpad, *mysinkpad; + +#define BUFFER_CAPS_STRING_32 \ + "audio/x-raw, " \ + "channels = (int) 1, " \ + "rate = (int) 44100, " \ + "layout = (string) interleaved, " \ + "format = (string) " GST_AUDIO_NE(F32) + +#define BUFFER_CAPS_STRING_64 \ + "audio/x-raw, " \ + "channels = (int) 1, " \ + "rate = (int) 44100, " \ + "layout = (string) interleaved, " \ + "format = (string) " GST_AUDIO_NE(F64) + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "channels = (int) 1, " + "rate = (int) 44100, " + "layout = (string) interleaved, " + "format = (string) { " + GST_AUDIO_NE (F32) ", " GST_AUDIO_NE (F64) " }")); +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "channels = (int) 1, " + "rate = (int) 44100, " + "layout = (string) interleaved, " + "format = (string) { " + GST_AUDIO_NE (F32) ", " GST_AUDIO_NE (F64) " }")); + +static GstElement * +setup_audiochebband (void) +{ + GstElement *audiochebband; + + GST_DEBUG ("setup_audiochebband"); + audiochebband = gst_check_setup_element ("audiochebband"); + mysrcpad = gst_check_setup_src_pad (audiochebband, &srctemplate); + mysinkpad = gst_check_setup_sink_pad (audiochebband, &sinktemplate); + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + + return audiochebband; +} + +static void +cleanup_audiochebband (GstElement * audiochebband) +{ + GST_DEBUG ("cleanup_audiochebband"); + + g_list_foreach (buffers, (GFunc) gst_mini_object_unref, NULL); + g_list_free (buffers); + buffers = NULL; + + gst_pad_set_active (mysrcpad, FALSE); + gst_pad_set_active (mysinkpad, FALSE); + gst_check_teardown_src_pad (audiochebband); + gst_check_teardown_sink_pad (audiochebband); + gst_check_teardown_element (audiochebband); +} + +/* Test if data containing only one frequency component + * at 0 is erased with bandpass mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_type1_32_bp_0hz) +{ + GstElement *audiochebband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in, *res, rms; + gint i; + GstMapInfo map; + + audiochebband = setup_audiochebband (); + /* Set to bandpass */ + g_object_set (G_OBJECT (audiochebband), "mode", 0, NULL); + g_object_set (G_OBJECT (audiochebband), "poles", 8, NULL); + g_object_set (G_OBJECT (audiochebband), "type", 1, NULL); + g_object_set (G_OBJECT (audiochebband), "ripple", 0.25, NULL); + + fail_unless (gst_element_set_state (audiochebband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiochebband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiochebband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_allocate (NULL, 1024 * sizeof (gfloat), NULL); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 1024; i++) + in[i] = 1.0; + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiochebband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + + rms = 0.0; + for (i = 0; i < 1024; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 1024.0); + fail_unless (rms <= 0.1); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiochebband (audiochebband); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at band center is preserved with bandpass mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_type1_32_bp_11025hz) +{ + GstElement *audiochebband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in, *res, rms; + gint i; + GstMapInfo map; + + audiochebband = setup_audiochebband (); + /* Set to bandpass */ + g_object_set (G_OBJECT (audiochebband), "mode", 0, NULL); + g_object_set (G_OBJECT (audiochebband), "poles", 8, NULL); + g_object_set (G_OBJECT (audiochebband), "type", 1, NULL); + g_object_set (G_OBJECT (audiochebband), "ripple", 0.25, NULL); + + fail_unless (gst_element_set_state (audiochebband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiochebband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiochebband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_allocate (NULL, 1024 * sizeof (gfloat), NULL); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 1024; i += 4) { + in[i] = 0.0; + in[i + 1] = 1.0; + in[i + 2] = 0.0; + in[i + 3] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiochebband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + + rms = 0.0; + for (i = 0; i < 1024; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 1024.0); + fail_unless (rms >= 0.6); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiochebband (audiochebband); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at rate/2 is erased with bandpass mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_type1_32_bp_22050hz) +{ + GstElement *audiochebband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in, *res, rms; + gint i; + GstMapInfo map; + + audiochebband = setup_audiochebband (); + /* Set to bandpass */ + g_object_set (G_OBJECT (audiochebband), "mode", 0, NULL); + g_object_set (G_OBJECT (audiochebband), "poles", 8, NULL); + g_object_set (G_OBJECT (audiochebband), "type", 1, NULL); + g_object_set (G_OBJECT (audiochebband), "ripple", 0.25, NULL); + + fail_unless (gst_element_set_state (audiochebband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiochebband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiochebband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_allocate (NULL, 1024 * sizeof (gfloat), NULL); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 1024; i += 2) { + in[i] = 1.0; + in[i + 1] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiochebband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + + rms = 0.0; + for (i = 0; i < 1024; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 1024.0); + fail_unless (rms <= 0.1); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiochebband (audiochebband); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at 0 is preserved with bandreject mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_type1_32_br_0hz) +{ + GstElement *audiochebband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in, *res, rms; + gint i; + GstMapInfo map; + + audiochebband = setup_audiochebband (); + /* Set to bandreject */ + g_object_set (G_OBJECT (audiochebband), "mode", 1, NULL); + g_object_set (G_OBJECT (audiochebband), "poles", 8, NULL); + g_object_set (G_OBJECT (audiochebband), "type", 1, NULL); + g_object_set (G_OBJECT (audiochebband), "ripple", 0.25, NULL); + + fail_unless (gst_element_set_state (audiochebband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiochebband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiochebband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_allocate (NULL, 1024 * sizeof (gfloat), NULL); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 1024; i++) + in[i] = 1.0; + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiochebband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + + rms = 0.0; + for (i = 0; i < 1024; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 1024.0); + fail_unless (rms >= 0.9); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiochebband (audiochebband); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at band center is erased with bandreject mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_type1_32_br_11025hz) +{ + GstElement *audiochebband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in, *res, rms; + gint i; + GstMapInfo map; + + audiochebband = setup_audiochebband (); + /* Set to bandreject */ + g_object_set (G_OBJECT (audiochebband), "mode", 1, NULL); + g_object_set (G_OBJECT (audiochebband), "poles", 8, NULL); + g_object_set (G_OBJECT (audiochebband), "type", 1, NULL); + g_object_set (G_OBJECT (audiochebband), "ripple", 0.25, NULL); + + fail_unless (gst_element_set_state (audiochebband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiochebband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiochebband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_allocate (NULL, 1024 * sizeof (gfloat), NULL); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 1024; i += 4) { + in[i] = 0.0; + in[i + 1] = 1.0; + in[i + 2] = 0.0; + in[i + 3] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiochebband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + + rms = 0.0; + for (i = 0; i < 1024; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 1024.0); + fail_unless (rms <= 0.1); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiochebband (audiochebband); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at rate/2 is preserved with bandreject mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_type1_32_br_22050hz) +{ + GstElement *audiochebband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in, *res, rms; + gint i; + GstMapInfo map; + + audiochebband = setup_audiochebband (); + /* Set to bandreject */ + g_object_set (G_OBJECT (audiochebband), "mode", 1, NULL); + g_object_set (G_OBJECT (audiochebband), "poles", 8, NULL); + g_object_set (G_OBJECT (audiochebband), "type", 1, NULL); + g_object_set (G_OBJECT (audiochebband), "ripple", 0.25, NULL); + + fail_unless (gst_element_set_state (audiochebband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiochebband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiochebband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_allocate (NULL, 1024 * sizeof (gfloat), NULL); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 1024; i += 2) { + in[i] = 1.0; + in[i + 1] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiochebband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + + rms = 0.0; + for (i = 0; i < 1024; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 1024.0); + fail_unless (rms >= 0.9); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiochebband (audiochebband); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at 0 is erased with bandpass mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_type1_64_bp_0hz) +{ + GstElement *audiochebband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in, *res, rms; + gint i; + GstMapInfo map; + + audiochebband = setup_audiochebband (); + /* Set to bandpass */ + g_object_set (G_OBJECT (audiochebband), "mode", 0, NULL); + g_object_set (G_OBJECT (audiochebband), "poles", 8, NULL); + g_object_set (G_OBJECT (audiochebband), "type", 1, NULL); + g_object_set (G_OBJECT (audiochebband), "ripple", 0.25, NULL); + + fail_unless (gst_element_set_state (audiochebband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiochebband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiochebband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_and_alloc (1024 * sizeof (gdouble)); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 1024; i++) + in[i] = 1.0; + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiochebband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + + rms = 0.0; + for (i = 0; i < 1024; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 1024.0); + fail_unless (rms <= 0.1); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiochebband (audiochebband); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at band center is preserved with bandpass mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_type1_64_bp_11025hz) +{ + GstElement *audiochebband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in, *res, rms; + gint i; + GstMapInfo map; + + audiochebband = setup_audiochebband (); + /* Set to bandpass */ + g_object_set (G_OBJECT (audiochebband), "mode", 0, NULL); + g_object_set (G_OBJECT (audiochebband), "poles", 8, NULL); + g_object_set (G_OBJECT (audiochebband), "type", 1, NULL); + g_object_set (G_OBJECT (audiochebband), "ripple", 0.25, NULL); + + fail_unless (gst_element_set_state (audiochebband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiochebband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiochebband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_and_alloc (1024 * sizeof (gdouble)); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 1024; i += 4) { + in[i] = 0.0; + in[i + 1] = 1.0; + in[i + 2] = 0.0; + in[i + 3] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiochebband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + + rms = 0.0; + for (i = 0; i < 1024; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 1024.0); + fail_unless (rms >= 0.6); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiochebband (audiochebband); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at rate/2 is erased with bandpass mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_type1_64_bp_22050hz) +{ + GstElement *audiochebband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in, *res, rms; + gint i; + GstMapInfo map; + + audiochebband = setup_audiochebband (); + /* Set to bandpass */ + g_object_set (G_OBJECT (audiochebband), "mode", 0, NULL); + g_object_set (G_OBJECT (audiochebband), "poles", 8, NULL); + g_object_set (G_OBJECT (audiochebband), "type", 1, NULL); + g_object_set (G_OBJECT (audiochebband), "ripple", 0.25, NULL); + + fail_unless (gst_element_set_state (audiochebband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiochebband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiochebband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_and_alloc (1024 * sizeof (gdouble)); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 1024; i += 2) { + in[i] = 1.0; + in[i + 1] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiochebband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + + rms = 0.0; + for (i = 0; i < 1024; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 1024.0); + fail_unless (rms <= 0.1); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiochebband (audiochebband); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at 0 is preserved with bandreject mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_type1_64_br_0hz) +{ + GstElement *audiochebband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in, *res, rms; + gint i; + GstMapInfo map; + + audiochebband = setup_audiochebband (); + /* Set to bandreject */ + g_object_set (G_OBJECT (audiochebband), "mode", 1, NULL); + g_object_set (G_OBJECT (audiochebband), "poles", 8, NULL); + g_object_set (G_OBJECT (audiochebband), "type", 1, NULL); + g_object_set (G_OBJECT (audiochebband), "ripple", 0.25, NULL); + + fail_unless (gst_element_set_state (audiochebband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiochebband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiochebband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_and_alloc (1024 * sizeof (gdouble)); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 1024; i++) + in[i] = 1.0; + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiochebband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + + rms = 0.0; + for (i = 0; i < 1024; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 1024.0); + fail_unless (rms >= 0.9); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiochebband (audiochebband); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at band center is erased with bandreject mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_type1_64_br_11025hz) +{ + GstElement *audiochebband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in, *res, rms; + gint i; + GstMapInfo map; + + audiochebband = setup_audiochebband (); + /* Set to bandreject */ + g_object_set (G_OBJECT (audiochebband), "mode", 1, NULL); + g_object_set (G_OBJECT (audiochebband), "poles", 8, NULL); + g_object_set (G_OBJECT (audiochebband), "type", 1, NULL); + g_object_set (G_OBJECT (audiochebband), "ripple", 0.25, NULL); + + fail_unless (gst_element_set_state (audiochebband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiochebband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiochebband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_and_alloc (1024 * sizeof (gdouble)); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 1024; i += 4) { + in[i] = 0.0; + in[i + 1] = 1.0; + in[i + 2] = 0.0; + in[i + 3] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiochebband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + + rms = 0.0; + for (i = 0; i < 1024; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 1024.0); + fail_unless (rms <= 0.1); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiochebband (audiochebband); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at rate/2 is preserved with bandreject mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_type1_64_br_22050hz) +{ + GstElement *audiochebband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in, *res, rms; + gint i; + GstMapInfo map; + + audiochebband = setup_audiochebband (); + /* Set to bandreject */ + g_object_set (G_OBJECT (audiochebband), "mode", 1, NULL); + g_object_set (G_OBJECT (audiochebband), "poles", 8, NULL); + g_object_set (G_OBJECT (audiochebband), "type", 1, NULL); + g_object_set (G_OBJECT (audiochebband), "ripple", 0.25, NULL); + + fail_unless (gst_element_set_state (audiochebband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiochebband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiochebband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_and_alloc (1024 * sizeof (gdouble)); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 1024; i += 2) { + in[i] = 1.0; + in[i + 1] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiochebband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + + rms = 0.0; + for (i = 0; i < 1024; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 1024.0); + fail_unless (rms >= 0.9); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiochebband (audiochebband); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at 0 is erased with bandpass mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_type2_32_bp_0hz) +{ + GstElement *audiochebband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in, *res, rms; + gint i; + GstMapInfo map; + + audiochebband = setup_audiochebband (); + /* Set to bandpass */ + g_object_set (G_OBJECT (audiochebband), "mode", 0, NULL); + g_object_set (G_OBJECT (audiochebband), "poles", 8, NULL); + g_object_set (G_OBJECT (audiochebband), "type", 2, NULL); + g_object_set (G_OBJECT (audiochebband), "ripple", 40.0, NULL); + + fail_unless (gst_element_set_state (audiochebband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiochebband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiochebband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_allocate (NULL, 1024 * sizeof (gfloat), NULL); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 1024; i++) + in[i] = 1.0; + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiochebband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + + rms = 0.0; + for (i = 0; i < 1024; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 1024.0); + fail_unless (rms <= 0.1); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiochebband (audiochebband); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at band center is preserved with bandpass mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_type2_32_bp_11025hz) +{ + GstElement *audiochebband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in, *res, rms; + gint i; + GstMapInfo map; + + audiochebband = setup_audiochebband (); + /* Set to bandpass */ + g_object_set (G_OBJECT (audiochebband), "mode", 0, NULL); + g_object_set (G_OBJECT (audiochebband), "poles", 8, NULL); + g_object_set (G_OBJECT (audiochebband), "type", 2, NULL); + g_object_set (G_OBJECT (audiochebband), "ripple", 40.0, NULL); + + fail_unless (gst_element_set_state (audiochebband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiochebband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiochebband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_allocate (NULL, 1024 * sizeof (gfloat), NULL); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 1024; i += 4) { + in[i] = 0.0; + in[i + 1] = 1.0; + in[i + 2] = 0.0; + in[i + 3] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiochebband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + + rms = 0.0; + for (i = 0; i < 1024; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 1024.0); + fail_unless (rms >= 0.6); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiochebband (audiochebband); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at rate/2 is erased with bandpass mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_type2_32_bp_22050hz) +{ + GstElement *audiochebband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in, *res, rms; + gint i; + GstMapInfo map; + + audiochebband = setup_audiochebband (); + /* Set to bandpass */ + g_object_set (G_OBJECT (audiochebband), "mode", 0, NULL); + g_object_set (G_OBJECT (audiochebband), "poles", 8, NULL); + g_object_set (G_OBJECT (audiochebband), "type", 2, NULL); + g_object_set (G_OBJECT (audiochebband), "ripple", 40.0, NULL); + + fail_unless (gst_element_set_state (audiochebband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiochebband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiochebband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_allocate (NULL, 1024 * sizeof (gfloat), NULL); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 1024; i += 2) { + in[i] = 1.0; + in[i + 1] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiochebband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + + rms = 0.0; + for (i = 0; i < 1024; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 1024.0); + fail_unless (rms <= 0.1); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiochebband (audiochebband); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at 0 is preserved with bandreject mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_type2_32_br_0hz) +{ + GstElement *audiochebband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in, *res, rms; + gint i; + GstMapInfo map; + + audiochebband = setup_audiochebband (); + /* Set to bandreject */ + g_object_set (G_OBJECT (audiochebband), "mode", 1, NULL); + g_object_set (G_OBJECT (audiochebband), "poles", 8, NULL); + g_object_set (G_OBJECT (audiochebband), "type", 2, NULL); + g_object_set (G_OBJECT (audiochebband), "ripple", 40.0, NULL); + + fail_unless (gst_element_set_state (audiochebband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiochebband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiochebband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_allocate (NULL, 1024 * sizeof (gfloat), NULL); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 1024; i++) + in[i] = 1.0; + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiochebband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + + rms = 0.0; + for (i = 0; i < 1024; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 1024.0); + fail_unless (rms >= 0.9); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiochebband (audiochebband); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at band center is erased with bandreject mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_type2_32_br_11025hz) +{ + GstElement *audiochebband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in, *res, rms; + gint i; + GstMapInfo map; + + audiochebband = setup_audiochebband (); + /* Set to bandreject */ + g_object_set (G_OBJECT (audiochebband), "mode", 1, NULL); + g_object_set (G_OBJECT (audiochebband), "poles", 8, NULL); + g_object_set (G_OBJECT (audiochebband), "type", 2, NULL); + g_object_set (G_OBJECT (audiochebband), "ripple", 40.0, NULL); + + fail_unless (gst_element_set_state (audiochebband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiochebband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiochebband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_allocate (NULL, 1024 * sizeof (gfloat), NULL); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 1024; i += 4) { + in[i] = 0.0; + in[i + 1] = 1.0; + in[i + 2] = 0.0; + in[i + 3] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiochebband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + + rms = 0.0; + for (i = 0; i < 1024; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 1024.0); + fail_unless (rms <= 0.1); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiochebband (audiochebband); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at rate/2 is preserved with bandreject mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_type2_32_br_22050hz) +{ + GstElement *audiochebband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in, *res, rms; + gint i; + GstMapInfo map; + + audiochebband = setup_audiochebband (); + /* Set to bandreject */ + g_object_set (G_OBJECT (audiochebband), "mode", 1, NULL); + g_object_set (G_OBJECT (audiochebband), "poles", 8, NULL); + g_object_set (G_OBJECT (audiochebband), "type", 2, NULL); + g_object_set (G_OBJECT (audiochebband), "ripple", 40.0, NULL); + + fail_unless (gst_element_set_state (audiochebband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiochebband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiochebband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_allocate (NULL, 1024 * sizeof (gfloat), NULL); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 1024; i += 2) { + in[i] = 1.0; + in[i + 1] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiochebband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + + rms = 0.0; + for (i = 0; i < 1024; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 1024.0); + fail_unless (rms >= 0.9); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiochebband (audiochebband); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at 0 is erased with bandpass mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_type2_64_bp_0hz) +{ + GstElement *audiochebband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in, *res, rms; + gint i; + GstMapInfo map; + + audiochebband = setup_audiochebband (); + /* Set to bandpass */ + g_object_set (G_OBJECT (audiochebband), "mode", 0, NULL); + g_object_set (G_OBJECT (audiochebband), "poles", 8, NULL); + g_object_set (G_OBJECT (audiochebband), "type", 2, NULL); + g_object_set (G_OBJECT (audiochebband), "ripple", 40.0, NULL); + + fail_unless (gst_element_set_state (audiochebband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiochebband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiochebband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_and_alloc (1024 * sizeof (gdouble)); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 1024; i++) + in[i] = 1.0; + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiochebband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + + rms = 0.0; + for (i = 0; i < 1024; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 1024.0); + fail_unless (rms <= 0.1); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiochebband (audiochebband); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at band center is preserved with bandpass mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_type2_64_bp_11025hz) +{ + GstElement *audiochebband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in, *res, rms; + gint i; + GstMapInfo map; + + audiochebband = setup_audiochebband (); + /* Set to bandpass */ + g_object_set (G_OBJECT (audiochebband), "mode", 0, NULL); + g_object_set (G_OBJECT (audiochebband), "poles", 8, NULL); + g_object_set (G_OBJECT (audiochebband), "type", 2, NULL); + g_object_set (G_OBJECT (audiochebband), "ripple", 40.0, NULL); + + fail_unless (gst_element_set_state (audiochebband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiochebband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiochebband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_and_alloc (1024 * sizeof (gdouble)); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 1024; i += 4) { + in[i] = 0.0; + in[i + 1] = 1.0; + in[i + 2] = 0.0; + in[i + 3] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiochebband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + + rms = 0.0; + for (i = 0; i < 1024; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 1024.0); + fail_unless (rms >= 0.6); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiochebband (audiochebband); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at rate/2 is erased with bandpass mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_type2_64_bp_22050hz) +{ + GstElement *audiochebband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in, *res, rms; + gint i; + GstMapInfo map; + + audiochebband = setup_audiochebband (); + /* Set to bandpass */ + g_object_set (G_OBJECT (audiochebband), "mode", 0, NULL); + g_object_set (G_OBJECT (audiochebband), "poles", 8, NULL); + g_object_set (G_OBJECT (audiochebband), "type", 2, NULL); + g_object_set (G_OBJECT (audiochebband), "ripple", 40.0, NULL); + + fail_unless (gst_element_set_state (audiochebband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiochebband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiochebband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_and_alloc (1024 * sizeof (gdouble)); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 1024; i += 2) { + in[i] = 1.0; + in[i + 1] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiochebband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + + rms = 0.0; + for (i = 0; i < 1024; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 1024.0); + fail_unless (rms <= 0.1); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiochebband (audiochebband); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at 0 is preserved with bandreject mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_type2_64_br_0hz) +{ + GstElement *audiochebband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in, *res, rms; + gint i; + GstMapInfo map; + + audiochebband = setup_audiochebband (); + /* Set to bandreject */ + g_object_set (G_OBJECT (audiochebband), "mode", 1, NULL); + g_object_set (G_OBJECT (audiochebband), "poles", 8, NULL); + g_object_set (G_OBJECT (audiochebband), "type", 2, NULL); + g_object_set (G_OBJECT (audiochebband), "ripple", 40.0, NULL); + + fail_unless (gst_element_set_state (audiochebband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiochebband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiochebband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_and_alloc (1024 * sizeof (gdouble)); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 1024; i++) + in[i] = 1.0; + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiochebband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + + rms = 0.0; + for (i = 0; i < 1024; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 1024.0); + fail_unless (rms >= 0.9); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiochebband (audiochebband); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at band center is erased with bandreject mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_type2_64_br_11025hz) +{ + GstElement *audiochebband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in, *res, rms; + gint i; + GstMapInfo map; + + audiochebband = setup_audiochebband (); + /* Set to bandreject */ + g_object_set (G_OBJECT (audiochebband), "mode", 1, NULL); + g_object_set (G_OBJECT (audiochebband), "poles", 8, NULL); + g_object_set (G_OBJECT (audiochebband), "type", 2, NULL); + g_object_set (G_OBJECT (audiochebband), "ripple", 40.0, NULL); + + fail_unless (gst_element_set_state (audiochebband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiochebband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiochebband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_and_alloc (1024 * sizeof (gdouble)); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 1024; i += 4) { + in[i] = 0.0; + in[i + 1] = 1.0; + in[i + 2] = 0.0; + in[i + 3] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiochebband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + + rms = 0.0; + for (i = 0; i < 1024; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 1024.0); + fail_unless (rms <= 0.1); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiochebband (audiochebband); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at rate/2 is preserved with bandreject mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_type2_64_br_22050hz) +{ + GstElement *audiochebband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in, *res, rms; + gint i; + GstMapInfo map; + + audiochebband = setup_audiochebband (); + /* Set to bandreject */ + g_object_set (G_OBJECT (audiochebband), "mode", 1, NULL); + g_object_set (G_OBJECT (audiochebband), "poles", 8, NULL); + g_object_set (G_OBJECT (audiochebband), "type", 2, NULL); + g_object_set (G_OBJECT (audiochebband), "ripple", 40.0, NULL); + + fail_unless (gst_element_set_state (audiochebband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiochebband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiochebband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_and_alloc (1024 * sizeof (gdouble)); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 1024; i += 2) { + in[i] = 1.0; + in[i + 1] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiochebband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + + rms = 0.0; + for (i = 0; i < 1024; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 1024.0); + fail_unless (rms >= 0.9); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiochebband (audiochebband); +} + +GST_END_TEST; + +static Suite * +audiochebband_suite (void) +{ + Suite *s = suite_create ("audiochebband"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_type1_32_bp_0hz); + tcase_add_test (tc_chain, test_type1_32_bp_11025hz); + tcase_add_test (tc_chain, test_type1_32_bp_22050hz); + tcase_add_test (tc_chain, test_type1_32_br_0hz); + tcase_add_test (tc_chain, test_type1_32_br_11025hz); + tcase_add_test (tc_chain, test_type1_32_br_22050hz); + tcase_add_test (tc_chain, test_type1_64_bp_0hz); + tcase_add_test (tc_chain, test_type1_64_bp_11025hz); + tcase_add_test (tc_chain, test_type1_64_bp_22050hz); + tcase_add_test (tc_chain, test_type1_64_br_0hz); + tcase_add_test (tc_chain, test_type1_64_br_11025hz); + tcase_add_test (tc_chain, test_type1_64_br_22050hz); + tcase_add_test (tc_chain, test_type2_32_bp_0hz); + tcase_add_test (tc_chain, test_type2_32_bp_11025hz); + tcase_add_test (tc_chain, test_type2_32_bp_22050hz); + tcase_add_test (tc_chain, test_type2_32_br_0hz); + tcase_add_test (tc_chain, test_type2_32_br_11025hz); + tcase_add_test (tc_chain, test_type2_32_br_22050hz); + tcase_add_test (tc_chain, test_type2_64_bp_0hz); + tcase_add_test (tc_chain, test_type2_64_bp_11025hz); + tcase_add_test (tc_chain, test_type2_64_bp_22050hz); + tcase_add_test (tc_chain, test_type2_64_br_0hz); + tcase_add_test (tc_chain, test_type2_64_br_11025hz); + tcase_add_test (tc_chain, test_type2_64_br_22050hz); + + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = audiochebband_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/audiocheblimit.c b/tests/check/elements/audiocheblimit.c new file mode 100644 index 0000000..c155b20 --- /dev/null +++ b/tests/check/elements/audiocheblimit.c @@ -0,0 +1,1101 @@ +/* GStreamer + * + * Copyright (C) 2007 Sebastian Dröge <slomo@circular-chaos.org> + * + * audiocheblimit.c: Unit test for the audiocheblimit element + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <gst/gst.h> +#include <gst/audio/audio.h> +#include <gst/base/gstbasetransform.h> +#include <gst/check/gstcheck.h> + +#include <math.h> + +/* 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 */ +GstPad *mysrcpad, *mysinkpad; + +#define BUFFER_CAPS_STRING_32 \ + "audio/x-raw, " \ + "channels = (int) 1, " \ + "rate = (int) 44100, " \ + "layout = (string) interleaved, " \ + "format = (string) " GST_AUDIO_NE(F32) + +#define BUFFER_CAPS_STRING_64 \ + "audio/x-raw, " \ + "channels = (int) 1, " \ + "rate = (int) 44100, " \ + "layout = (string) interleaved, " \ + "format = (string) " GST_AUDIO_NE(F64) + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "channels = (int) 1, " + "rate = (int) 44100, " + "layout = (string) interleaved, " + "format = (string) { " + GST_AUDIO_NE (F32) ", " GST_AUDIO_NE (F64) " }")); +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "channels = (int) 1, " + "rate = (int) 44100, " + "layout = (string) interleaved, " + "format = (string) { " + GST_AUDIO_NE (F32) ", " GST_AUDIO_NE (F64) " }")); + +static GstElement * +setup_audiocheblimit (void) +{ + GstElement *audiocheblimit; + + GST_DEBUG ("setup_audiocheblimit"); + audiocheblimit = gst_check_setup_element ("audiocheblimit"); + mysrcpad = gst_check_setup_src_pad (audiocheblimit, &srctemplate); + mysinkpad = gst_check_setup_sink_pad (audiocheblimit, &sinktemplate); + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + + return audiocheblimit; +} + +static void +cleanup_audiocheblimit (GstElement * audiocheblimit) +{ + GST_DEBUG ("cleanup_audiocheblimit"); + + g_list_foreach (buffers, (GFunc) gst_mini_object_unref, NULL); + g_list_free (buffers); + buffers = NULL; + + gst_pad_set_active (mysrcpad, FALSE); + gst_pad_set_active (mysinkpad, FALSE); + gst_check_teardown_src_pad (audiocheblimit); + gst_check_teardown_sink_pad (audiocheblimit); + gst_check_teardown_element (audiocheblimit); +} + +/* Test if data containing only one frequency component + * at 0 is preserved with lowpass mode and a cutoff + * at rate/4 */ +GST_START_TEST (test_type1_32_lp_0hz) +{ + GstElement *audiocheblimit; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in, *res, rms; + gint i; + GstMapInfo map; + + audiocheblimit = setup_audiocheblimit (); + /* Set to lowpass */ + g_object_set (G_OBJECT (audiocheblimit), "mode", 0, NULL); + g_object_set (G_OBJECT (audiocheblimit), "poles", 8, NULL); + g_object_set (G_OBJECT (audiocheblimit), "type", 1, NULL); + g_object_set (G_OBJECT (audiocheblimit), "ripple", 0.25, NULL); + + fail_unless (gst_element_set_state (audiocheblimit, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiocheblimit), "cutoff", 44100 / 4.0, NULL); + inbuffer = gst_buffer_new_allocate (NULL, 128 * sizeof (gfloat), NULL); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 128; i++) + in[i] = 1.0; + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiocheblimit, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + + rms = 0.0; + for (i = 0; i < 128; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 128.0); + fail_unless (rms >= 0.9); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiocheblimit (audiocheblimit); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at rate/2 is erased with lowpass mode and a cutoff + * at rate/4 */ +GST_START_TEST (test_type1_32_lp_22050hz) +{ + GstElement *audiocheblimit; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in, *res, rms; + gint i; + GstMapInfo map; + + audiocheblimit = setup_audiocheblimit (); + /* Set to lowpass */ + g_object_set (G_OBJECT (audiocheblimit), "mode", 0, NULL); + g_object_set (G_OBJECT (audiocheblimit), "poles", 8, NULL); + g_object_set (G_OBJECT (audiocheblimit), "type", 1, NULL); + g_object_set (G_OBJECT (audiocheblimit), "ripple", 0.25, NULL); + + fail_unless (gst_element_set_state (audiocheblimit, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiocheblimit), "cutoff", 44100 / 4.0, NULL); + inbuffer = gst_buffer_new_allocate (NULL, 128 * sizeof (gfloat), NULL); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 128; i += 2) { + in[i] = 1.0; + in[i + 1] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiocheblimit, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + + rms = 0.0; + for (i = 0; i < 128; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 128.0); + fail_unless (rms <= 0.1); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiocheblimit (audiocheblimit); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at 0 is erased with highpass mode and a cutoff + * at rate/4 */ +GST_START_TEST (test_type1_32_hp_0hz) +{ + GstElement *audiocheblimit; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in, *res, rms; + gint i; + GstMapInfo map; + + audiocheblimit = setup_audiocheblimit (); + /* Set to highpass */ + g_object_set (G_OBJECT (audiocheblimit), "mode", 1, NULL); + g_object_set (G_OBJECT (audiocheblimit), "poles", 8, NULL); + g_object_set (G_OBJECT (audiocheblimit), "type", 1, NULL); + g_object_set (G_OBJECT (audiocheblimit), "ripple", 0.25, NULL); + + fail_unless (gst_element_set_state (audiocheblimit, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiocheblimit), "cutoff", 44100 / 4.0, NULL); + inbuffer = gst_buffer_new_allocate (NULL, 128 * sizeof (gfloat), NULL); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 128; i++) + in[i] = 1.0; + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiocheblimit, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + + rms = 0.0; + for (i = 0; i < 128; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 128.0); + fail_unless (rms <= 0.1); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiocheblimit (audiocheblimit); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at rate/2 is preserved with highpass mode and a cutoff + * at rate/4 */ +GST_START_TEST (test_type1_32_hp_22050hz) +{ + GstElement *audiocheblimit; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in, *res, rms; + gint i; + GstMapInfo map; + + audiocheblimit = setup_audiocheblimit (); + /* Set to highpass */ + g_object_set (G_OBJECT (audiocheblimit), "mode", 1, NULL); + g_object_set (G_OBJECT (audiocheblimit), "poles", 8, NULL); + g_object_set (G_OBJECT (audiocheblimit), "type", 1, NULL); + g_object_set (G_OBJECT (audiocheblimit), "ripple", 0.25, NULL); + + fail_unless (gst_element_set_state (audiocheblimit, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiocheblimit), "cutoff", 44100 / 4.0, NULL); + inbuffer = gst_buffer_new_allocate (NULL, 128 * sizeof (gfloat), NULL); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 128; i += 2) { + in[i] = 1.0; + in[i + 1] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiocheblimit, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + + rms = 0.0; + for (i = 0; i < 128; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 128.0); + fail_unless (rms >= 0.9); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiocheblimit (audiocheblimit); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at 0 is preserved with lowpass mode and a cutoff + * at rate/4 */ +GST_START_TEST (test_type1_64_lp_0hz) +{ + GstElement *audiocheblimit; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in, *res, rms; + gint i; + GstMapInfo map; + + audiocheblimit = setup_audiocheblimit (); + /* Set to lowpass */ + g_object_set (G_OBJECT (audiocheblimit), "mode", 0, NULL); + g_object_set (G_OBJECT (audiocheblimit), "poles", 8, NULL); + g_object_set (G_OBJECT (audiocheblimit), "type", 1, NULL); + g_object_set (G_OBJECT (audiocheblimit), "ripple", 0.25, NULL); + + fail_unless (gst_element_set_state (audiocheblimit, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiocheblimit), "cutoff", 44100 / 4.0, NULL); + inbuffer = gst_buffer_new_allocate (NULL, 128 * sizeof (gdouble), NULL); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 128; i++) + in[i] = 1.0; + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiocheblimit, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + + rms = 0.0; + for (i = 0; i < 128; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 128.0); + fail_unless (rms >= 0.9); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiocheblimit (audiocheblimit); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at rate/2 is erased with lowpass mode and a cutoff + * at rate/4 */ +GST_START_TEST (test_type1_64_lp_22050hz) +{ + GstElement *audiocheblimit; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in, *res, rms; + gint i; + GstMapInfo map; + + audiocheblimit = setup_audiocheblimit (); + /* Set to lowpass */ + g_object_set (G_OBJECT (audiocheblimit), "mode", 0, NULL); + g_object_set (G_OBJECT (audiocheblimit), "poles", 8, NULL); + g_object_set (G_OBJECT (audiocheblimit), "type", 1, NULL); + g_object_set (G_OBJECT (audiocheblimit), "ripple", 0.25, NULL); + + fail_unless (gst_element_set_state (audiocheblimit, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiocheblimit), "cutoff", 44100 / 4.0, NULL); + inbuffer = gst_buffer_new_allocate (NULL, 128 * sizeof (gdouble), NULL); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 128; i += 2) { + in[i] = 1.0; + in[i + 1] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiocheblimit, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + + rms = 0.0; + for (i = 0; i < 128; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 128.0); + fail_unless (rms <= 0.1); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiocheblimit (audiocheblimit); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at 0 is erased with highpass mode and a cutoff + * at rate/4 */ +GST_START_TEST (test_type1_64_hp_0hz) +{ + GstElement *audiocheblimit; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in, *res, rms; + gint i; + GstMapInfo map; + + audiocheblimit = setup_audiocheblimit (); + /* Set to highpass */ + g_object_set (G_OBJECT (audiocheblimit), "mode", 1, NULL); + g_object_set (G_OBJECT (audiocheblimit), "poles", 8, NULL); + g_object_set (G_OBJECT (audiocheblimit), "type", 1, NULL); + g_object_set (G_OBJECT (audiocheblimit), "ripple", 0.25, NULL); + + fail_unless (gst_element_set_state (audiocheblimit, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiocheblimit), "cutoff", 44100 / 4.0, NULL); + inbuffer = gst_buffer_new_allocate (NULL, 128 * sizeof (gdouble), NULL); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 128; i++) + in[i] = 1.0; + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiocheblimit, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + + rms = 0.0; + for (i = 0; i < 128; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 128.0); + fail_unless (rms <= 0.1); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiocheblimit (audiocheblimit); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at rate/2 is preserved with highpass mode and a cutoff + * at rate/4 */ +GST_START_TEST (test_type1_64_hp_22050hz) +{ + GstElement *audiocheblimit; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in, *res, rms; + gint i; + GstMapInfo map; + + audiocheblimit = setup_audiocheblimit (); + /* Set to highpass */ + g_object_set (G_OBJECT (audiocheblimit), "mode", 1, NULL); + g_object_set (G_OBJECT (audiocheblimit), "poles", 8, NULL); + g_object_set (G_OBJECT (audiocheblimit), "type", 1, NULL); + g_object_set (G_OBJECT (audiocheblimit), "ripple", 0.25, NULL); + + fail_unless (gst_element_set_state (audiocheblimit, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiocheblimit), "cutoff", 44100 / 4.0, NULL); + inbuffer = gst_buffer_new_allocate (NULL, 128 * sizeof (gdouble), NULL); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 128; i += 2) { + in[i] = 1.0; + in[i + 1] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiocheblimit, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + + rms = 0.0; + for (i = 0; i < 128; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 128.0); + fail_unless (rms >= 0.9); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiocheblimit (audiocheblimit); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at 0 is preserved with lowpass mode and a cutoff + * at rate/4 */ +GST_START_TEST (test_type2_32_lp_0hz) +{ + GstElement *audiocheblimit; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in, *res, rms; + gint i; + GstMapInfo map; + + audiocheblimit = setup_audiocheblimit (); + /* Set to lowpass */ + g_object_set (G_OBJECT (audiocheblimit), "mode", 0, NULL); + g_object_set (G_OBJECT (audiocheblimit), "poles", 8, NULL); + g_object_set (G_OBJECT (audiocheblimit), "type", 2, NULL); + g_object_set (G_OBJECT (audiocheblimit), "ripple", 40.0, NULL); + + fail_unless (gst_element_set_state (audiocheblimit, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiocheblimit), "cutoff", 44100 / 4.0, NULL); + inbuffer = gst_buffer_new_allocate (NULL, 128 * sizeof (gfloat), NULL); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 128; i++) + in[i] = 1.0; + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiocheblimit, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + + rms = 0.0; + for (i = 0; i < 128; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 128.0); + fail_unless (rms >= 0.9); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiocheblimit (audiocheblimit); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at rate/2 is erased with lowpass mode and a cutoff + * at rate/4 */ +GST_START_TEST (test_type2_32_lp_22050hz) +{ + GstElement *audiocheblimit; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in, *res, rms; + gint i; + GstMapInfo map; + + audiocheblimit = setup_audiocheblimit (); + /* Set to lowpass */ + g_object_set (G_OBJECT (audiocheblimit), "mode", 0, NULL); + g_object_set (G_OBJECT (audiocheblimit), "poles", 8, NULL); + g_object_set (G_OBJECT (audiocheblimit), "type", 2, NULL); + g_object_set (G_OBJECT (audiocheblimit), "ripple", 40.0, NULL); + + fail_unless (gst_element_set_state (audiocheblimit, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiocheblimit), "cutoff", 44100 / 4.0, NULL); + inbuffer = gst_buffer_new_allocate (NULL, 128 * sizeof (gfloat), NULL); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 128; i += 2) { + in[i] = 1.0; + in[i + 1] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiocheblimit, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + + rms = 0.0; + for (i = 0; i < 128; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 128.0); + fail_unless (rms <= 0.1); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiocheblimit (audiocheblimit); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at 0 is erased with highpass mode and a cutoff + * at rate/4 */ +GST_START_TEST (test_type2_32_hp_0hz) +{ + GstElement *audiocheblimit; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in, *res, rms; + gint i; + GstMapInfo map; + + audiocheblimit = setup_audiocheblimit (); + /* Set to highpass */ + g_object_set (G_OBJECT (audiocheblimit), "mode", 1, NULL); + g_object_set (G_OBJECT (audiocheblimit), "poles", 8, NULL); + g_object_set (G_OBJECT (audiocheblimit), "type", 2, NULL); + g_object_set (G_OBJECT (audiocheblimit), "ripple", 40.0, NULL); + + fail_unless (gst_element_set_state (audiocheblimit, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiocheblimit), "cutoff", 44100 / 4.0, NULL); + inbuffer = gst_buffer_new_allocate (NULL, 128 * sizeof (gfloat), NULL); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 128; i++) + in[i] = 1.0; + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiocheblimit, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + + rms = 0.0; + for (i = 0; i < 128; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 128.0); + fail_unless (rms <= 0.1); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiocheblimit (audiocheblimit); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at rate/2 is preserved with highpass mode and a cutoff + * at rate/4 */ +GST_START_TEST (test_type2_32_hp_22050hz) +{ + GstElement *audiocheblimit; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in, *res, rms; + gint i; + GstMapInfo map; + + audiocheblimit = setup_audiocheblimit (); + /* Set to highpass */ + g_object_set (G_OBJECT (audiocheblimit), "mode", 1, NULL); + g_object_set (G_OBJECT (audiocheblimit), "poles", 8, NULL); + g_object_set (G_OBJECT (audiocheblimit), "type", 2, NULL); + g_object_set (G_OBJECT (audiocheblimit), "ripple", 40.0, NULL); + + fail_unless (gst_element_set_state (audiocheblimit, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiocheblimit), "cutoff", 44100 / 4.0, NULL); + inbuffer = gst_buffer_new_allocate (NULL, 128 * sizeof (gfloat), NULL); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 128; i += 2) { + in[i] = 1.0; + in[i + 1] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiocheblimit, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + + rms = 0.0; + for (i = 0; i < 128; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 128.0); + fail_unless (rms >= 0.9); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiocheblimit (audiocheblimit); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at 0 is preserved with lowpass mode and a cutoff + * at rate/4 */ +GST_START_TEST (test_type2_64_lp_0hz) +{ + GstElement *audiocheblimit; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in, *res, rms; + gint i; + GstMapInfo map; + + audiocheblimit = setup_audiocheblimit (); + /* Set to lowpass */ + g_object_set (G_OBJECT (audiocheblimit), "mode", 0, NULL); + g_object_set (G_OBJECT (audiocheblimit), "poles", 8, NULL); + g_object_set (G_OBJECT (audiocheblimit), "type", 2, NULL); + g_object_set (G_OBJECT (audiocheblimit), "ripple", 40.0, NULL); + + fail_unless (gst_element_set_state (audiocheblimit, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiocheblimit), "cutoff", 44100 / 4.0, NULL); + inbuffer = gst_buffer_new_and_alloc (128 * sizeof (gdouble)); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 128; i++) + in[i] = 1.0; + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiocheblimit, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + + rms = 0.0; + for (i = 0; i < 128; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 128.0); + fail_unless (rms >= 0.9); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiocheblimit (audiocheblimit); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at rate/2 is erased with lowpass mode and a cutoff + * at rate/4 */ +GST_START_TEST (test_type2_64_lp_22050hz) +{ + GstElement *audiocheblimit; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in, *res, rms; + gint i; + GstMapInfo map; + + audiocheblimit = setup_audiocheblimit (); + /* Set to lowpass */ + g_object_set (G_OBJECT (audiocheblimit), "mode", 0, NULL); + g_object_set (G_OBJECT (audiocheblimit), "poles", 8, NULL); + g_object_set (G_OBJECT (audiocheblimit), "type", 2, NULL); + g_object_set (G_OBJECT (audiocheblimit), "ripple", 40.0, NULL); + + fail_unless (gst_element_set_state (audiocheblimit, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiocheblimit), "cutoff", 44100 / 4.0, NULL); + inbuffer = gst_buffer_new_and_alloc (128 * sizeof (gdouble)); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 128; i += 2) { + in[i] = 1.0; + in[i + 1] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiocheblimit, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + + rms = 0.0; + for (i = 0; i < 128; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 128.0); + fail_unless (rms <= 0.1); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiocheblimit (audiocheblimit); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at 0 is erased with highpass mode and a cutoff + * at rate/4 */ +GST_START_TEST (test_type2_64_hp_0hz) +{ + GstElement *audiocheblimit; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in, *res, rms; + gint i; + GstMapInfo map; + + audiocheblimit = setup_audiocheblimit (); + /* Set to highpass */ + g_object_set (G_OBJECT (audiocheblimit), "mode", 1, NULL); + g_object_set (G_OBJECT (audiocheblimit), "poles", 8, NULL); + g_object_set (G_OBJECT (audiocheblimit), "type", 2, NULL); + g_object_set (G_OBJECT (audiocheblimit), "ripple", 40.0, NULL); + + fail_unless (gst_element_set_state (audiocheblimit, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiocheblimit), "cutoff", 44100 / 4.0, NULL); + inbuffer = gst_buffer_new_and_alloc (128 * sizeof (gdouble)); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 128; i++) + in[i] = 1.0; + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiocheblimit, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + + rms = 0.0; + for (i = 0; i < 128; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 128.0); + fail_unless (rms <= 0.1); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiocheblimit (audiocheblimit); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at rate/2 is preserved with highpass mode and a cutoff + * at rate/4 */ +GST_START_TEST (test_type2_64_hp_22050hz) +{ + GstElement *audiocheblimit; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in, *res, rms; + gint i; + GstMapInfo map; + + audiocheblimit = setup_audiocheblimit (); + /* Set to highpass */ + g_object_set (G_OBJECT (audiocheblimit), "mode", 1, NULL); + g_object_set (G_OBJECT (audiocheblimit), "poles", 8, NULL); + g_object_set (G_OBJECT (audiocheblimit), "type", 2, NULL); + g_object_set (G_OBJECT (audiocheblimit), "ripple", 40.0, NULL); + + fail_unless (gst_element_set_state (audiocheblimit, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiocheblimit), "cutoff", 44100 / 4.0, NULL); + inbuffer = gst_buffer_new_and_alloc (128 * sizeof (gdouble)); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 128; i += 2) { + in[i] = 1.0; + in[i + 1] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (BUFFER_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiocheblimit, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + + rms = 0.0; + for (i = 0; i < 128; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / 128.0); + fail_unless (rms >= 0.9); + + gst_buffer_unmap (outbuffer, &map); + + /* cleanup */ + cleanup_audiocheblimit (audiocheblimit); +} + +GST_END_TEST; + + +static Suite * +audiocheblimit_suite (void) +{ + Suite *s = suite_create ("audiocheblimit"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_type1_32_lp_0hz); + tcase_add_test (tc_chain, test_type1_32_lp_22050hz); + tcase_add_test (tc_chain, test_type1_32_hp_0hz); + tcase_add_test (tc_chain, test_type1_32_hp_22050hz); + tcase_add_test (tc_chain, test_type1_64_lp_0hz); + tcase_add_test (tc_chain, test_type1_64_lp_22050hz); + tcase_add_test (tc_chain, test_type1_64_hp_0hz); + tcase_add_test (tc_chain, test_type1_64_hp_22050hz); + tcase_add_test (tc_chain, test_type2_32_lp_0hz); + tcase_add_test (tc_chain, test_type2_32_lp_22050hz); + tcase_add_test (tc_chain, test_type2_32_hp_0hz); + tcase_add_test (tc_chain, test_type2_32_hp_22050hz); + tcase_add_test (tc_chain, test_type2_64_lp_0hz); + tcase_add_test (tc_chain, test_type2_64_lp_22050hz); + tcase_add_test (tc_chain, test_type2_64_hp_0hz); + tcase_add_test (tc_chain, test_type2_64_hp_22050hz); + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = audiocheblimit_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/audiodynamic.c b/tests/check/elements/audiodynamic.c new file mode 100644 index 0000000..779f8e9 --- /dev/null +++ b/tests/check/elements/audiodynamic.c @@ -0,0 +1,457 @@ +/* GStreamer + * + * unit test for audiodynamic + * + * Copyright (C) 2007 Sebastian Dröge <slomo@circular-chaos.org> + * + * Greatly based on the audiopanorama unit test + * Copyright (C) 2006 Stefan Kost <ensonic@users.sf.net> + * + * 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 <unistd.h> + +#include <gst/audio/audio.h> +#include <gst/base/gstbasetransform.h> +#include <gst/check/gstcheck.h> + +gboolean have_eos = FALSE; + +/* 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 */ +GstPad *mysrcpad, *mysinkpad; + + +#define DYNAMIC_CAPS_STRING \ + "audio/x-raw, " \ + "channels = (int) 1, " \ + "rate = (int) 44100, " \ + "layout = (string) interleaved, " \ + "format = (string) " GST_AUDIO_NE(S16) + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "channels = (int) 1, " + "rate = (int) [ 1, MAX ], " + "layout = (string) interleaved, " + "format = (string)" GST_AUDIO_NE (S16))); +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "channels = (int) 1, " + "rate = (int) [ 1, MAX ], " + "layout = (string) interleaved, " + "format = (string) " GST_AUDIO_NE (S16))); + +static GstElement * +setup_dynamic (void) +{ + GstElement *dynamic; + + GST_DEBUG ("setup_dynamic"); + dynamic = gst_check_setup_element ("audiodynamic"); + mysrcpad = gst_check_setup_src_pad (dynamic, &srctemplate); + mysinkpad = gst_check_setup_sink_pad (dynamic, &sinktemplate); + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + + return dynamic; +} + +static void +cleanup_dynamic (GstElement * dynamic) +{ + GST_DEBUG ("cleanup_dynamic"); + + g_list_foreach (buffers, (GFunc) gst_mini_object_unref, NULL); + g_list_free (buffers); + buffers = NULL; + + gst_pad_set_active (mysrcpad, FALSE); + gst_pad_set_active (mysinkpad, FALSE); + gst_check_teardown_src_pad (dynamic); + gst_check_teardown_sink_pad (dynamic); + gst_check_teardown_element (dynamic); +} + +GST_START_TEST (test_passthrough) +{ + GstElement *dynamic; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gint16 in[6] = { 24576, -16384, 256, -128, 0, -24576 }; + gint16 res[6]; + + dynamic = setup_dynamic (); + fail_unless (gst_element_set_state (dynamic, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + inbuffer = + gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY, in, 12, 0, 12, + NULL, NULL); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 12) == 0); + caps = gst_caps_from_string (DYNAMIC_CAPS_STRING); + gst_check_setup_events (mysrcpad, dynamic, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being collected on the global buffer list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + fail_unless (gst_buffer_extract (outbuffer, 0, res, 12) == 12); + GST_INFO + ("expected %+5d %+5d %+5d %+5d %+5d %+5d real %+5d %+5d %+5d %+5d %+5d %+5d", + in[0], in[1], in[2], in[3], in[4], in[5], res[0], res[1], res[2], res[3], + res[4], res[5]); + fail_unless (gst_buffer_memcmp (outbuffer, 0, in, 12) == 0); + + /* cleanup */ + cleanup_dynamic (dynamic); +} + +GST_END_TEST; + +GST_START_TEST (test_compress_hard_50_50) +{ + GstElement *dynamic; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gint16 in[8] = { -30000, 24576, -16384, 256, -128, 0, -24576, 30000 }; + gint16 res[8]; + + dynamic = setup_dynamic (); + g_object_set (G_OBJECT (dynamic), "mode", 0, NULL); + g_object_set (G_OBJECT (dynamic), "characteristics", 0, NULL); + g_object_set (G_OBJECT (dynamic), "ratio", 0.5, NULL); + g_object_set (G_OBJECT (dynamic), "threshold", 0.5, NULL); + fail_unless (gst_element_set_state (dynamic, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (16); + gst_buffer_fill (inbuffer, 0, in, 16); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 16) == 0); + caps = gst_caps_from_string (DYNAMIC_CAPS_STRING); + gst_check_setup_events (mysrcpad, dynamic, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + fail_unless (gst_buffer_extract (outbuffer, 0, res, 16) == 16); + + fail_unless (res[0] > in[0]); + fail_unless (res[1] < in[1]); + fail_unless (res[2] == in[2]); + fail_unless (res[3] == in[3]); + fail_unless (res[4] == in[4]); + fail_unless (res[5] == in[5]); + fail_unless (res[6] > in[6]); + fail_unless (res[7] < in[7]); + + /* cleanup */ + cleanup_dynamic (dynamic); +} + +GST_END_TEST; + +GST_START_TEST (test_compress_soft_50_50) +{ + GstElement *dynamic; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gint16 in[8] = { -30000, 24576, -16384, 256, -128, 0, -24576, 30000 }; + gint16 res[8]; + + dynamic = setup_dynamic (); + g_object_set (G_OBJECT (dynamic), "mode", 0, NULL); + g_object_set (G_OBJECT (dynamic), "characteristics", 1, NULL); + g_object_set (G_OBJECT (dynamic), "ratio", 0.5, NULL); + g_object_set (G_OBJECT (dynamic), "threshold", 0.5, NULL); + fail_unless (gst_element_set_state (dynamic, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (16); + gst_buffer_fill (inbuffer, 0, in, 16); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 16) == 0); + caps = gst_caps_from_string (DYNAMIC_CAPS_STRING); + gst_check_setup_events (mysrcpad, dynamic, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + fail_unless (gst_buffer_extract (outbuffer, 0, res, 16) == 16); + + fail_unless (res[0] > in[0]); + fail_unless (res[1] < in[1]); + fail_unless (res[2] == in[2]); + fail_unless (res[3] == in[3]); + fail_unless (res[4] == in[4]); + fail_unless (res[5] == in[5]); + fail_unless (res[6] > in[6]); + fail_unless (res[7] < in[7]); + + /* cleanup */ + cleanup_dynamic (dynamic); +} + +GST_END_TEST; + +GST_START_TEST (test_compress_hard_100_50) +{ + GstElement *dynamic; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gint16 in[8] = { -30000, 24576, -16384, 256, -128, 0, -24576, 30000 }; + gint16 res[8]; + + dynamic = setup_dynamic (); + g_object_set (G_OBJECT (dynamic), "mode", 0, NULL); + g_object_set (G_OBJECT (dynamic), "characteristics", 0, NULL); + g_object_set (G_OBJECT (dynamic), "ratio", 0.5, NULL); + g_object_set (G_OBJECT (dynamic), "threshold", 1.0, NULL); + fail_unless (gst_element_set_state (dynamic, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (16); + gst_buffer_fill (inbuffer, 0, in, 16); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 16) == 0); + caps = gst_caps_from_string (DYNAMIC_CAPS_STRING); + gst_check_setup_events (mysrcpad, dynamic, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + fail_unless (gst_buffer_extract (outbuffer, 0, res, 16) == 16); + + fail_unless (res[0] == in[0]); + fail_unless (res[1] == in[1]); + fail_unless (res[2] == in[2]); + fail_unless (res[3] == in[3]); + fail_unless (res[4] == in[4]); + fail_unless (res[5] == in[5]); + fail_unless (res[6] == in[6]); + fail_unless (res[7] == in[7]); + + /* cleanup */ + cleanup_dynamic (dynamic); +} + +GST_END_TEST; + + +GST_START_TEST (test_expand_hard_50_200) +{ + GstElement *dynamic; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gint16 in[8] = { -30000, 24576, -16383, 256, -128, 0, -24576, 30000 }; + gint16 res[8]; + + dynamic = setup_dynamic (); + g_object_set (G_OBJECT (dynamic), "mode", 1, NULL); + g_object_set (G_OBJECT (dynamic), "characteristics", 0, NULL); + g_object_set (G_OBJECT (dynamic), "ratio", 2.0, NULL); + g_object_set (G_OBJECT (dynamic), "threshold", 0.5, NULL); + fail_unless (gst_element_set_state (dynamic, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (16); + gst_buffer_fill (inbuffer, 0, in, 16); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 16) == 0); + caps = gst_caps_from_string (DYNAMIC_CAPS_STRING); + gst_check_setup_events (mysrcpad, dynamic, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + fail_unless (gst_buffer_extract (outbuffer, 0, res, 16) == 16); + + fail_unless (res[0] == in[0]); + fail_unless (res[1] == in[1]); + fail_unless (res[2] > in[2]); + fail_unless (res[3] < in[3]); + fail_unless (res[4] > in[4]); + fail_unless (res[5] == in[5]); + fail_unless (res[6] == in[6]); + fail_unless (res[7] == in[7]); + + /* cleanup */ + cleanup_dynamic (dynamic); +} + +GST_END_TEST; + +GST_START_TEST (test_expand_soft_50_200) +{ + GstElement *dynamic; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gint16 in[8] = { -30000, 24576, -16383, 256, -128, 0, -24576, 30000 }; + gint16 res[8]; + + dynamic = setup_dynamic (); + g_object_set (G_OBJECT (dynamic), "mode", 1, NULL); + g_object_set (G_OBJECT (dynamic), "characteristics", 1, NULL); + g_object_set (G_OBJECT (dynamic), "ratio", 2.0, NULL); + g_object_set (G_OBJECT (dynamic), "threshold", 0.5, NULL); + fail_unless (gst_element_set_state (dynamic, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (16); + gst_buffer_fill (inbuffer, 0, in, 16); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 16) == 0); + caps = gst_caps_from_string (DYNAMIC_CAPS_STRING); + gst_check_setup_events (mysrcpad, dynamic, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + fail_unless (gst_buffer_extract (outbuffer, 0, res, 16) == 16); + + fail_unless (res[0] == in[0]); + fail_unless (res[1] == in[1]); + fail_unless (res[2] > in[2]); + fail_unless (res[3] < in[3]); + fail_unless (res[4] > in[4]); + fail_unless (res[5] == in[5]); + fail_unless (res[6] == in[6]); + fail_unless (res[7] == in[7]); + + /* cleanup */ + cleanup_dynamic (dynamic); +} + +GST_END_TEST; + +GST_START_TEST (test_expand_hard_0_200) +{ + GstElement *dynamic; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gint16 in[8] = { -30000, 24576, -16383, 256, -128, 0, -24576, 30000 }; + gint16 res[8]; + + dynamic = setup_dynamic (); + g_object_set (G_OBJECT (dynamic), "mode", 1, NULL); + g_object_set (G_OBJECT (dynamic), "characteristics", 0, NULL); + g_object_set (G_OBJECT (dynamic), "ratio", 2.0, NULL); + g_object_set (G_OBJECT (dynamic), "threshold", 0.0, NULL); + fail_unless (gst_element_set_state (dynamic, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (16); + gst_buffer_fill (inbuffer, 0, in, 16); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 16) == 0); + caps = gst_caps_from_string (DYNAMIC_CAPS_STRING); + gst_check_setup_events (mysrcpad, dynamic, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + fail_unless (gst_buffer_extract (outbuffer, 0, res, 16) == 16); + + fail_unless (res[0] == in[0]); + fail_unless (res[1] == in[1]); + fail_unless (res[2] == in[2]); + fail_unless (res[3] == in[3]); + fail_unless (res[4] == in[4]); + fail_unless (res[5] == in[5]); + fail_unless (res[6] == in[6]); + fail_unless (res[7] == in[7]); + + /* cleanup */ + cleanup_dynamic (dynamic); +} + +GST_END_TEST; + +static Suite * +dynamic_suite (void) +{ + Suite *s = suite_create ("dynamic"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_passthrough); + tcase_add_test (tc_chain, test_compress_hard_50_50); + tcase_add_test (tc_chain, test_compress_soft_50_50); + tcase_add_test (tc_chain, test_compress_hard_100_50); + tcase_add_test (tc_chain, test_expand_hard_50_200); + tcase_add_test (tc_chain, test_expand_soft_50_200); + tcase_add_test (tc_chain, test_expand_hard_0_200); + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = dynamic_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/audioecho.c b/tests/check/elements/audioecho.c new file mode 100644 index 0000000..025b710 --- /dev/null +++ b/tests/check/elements/audioecho.c @@ -0,0 +1,240 @@ +/* GStreamer + * + * Copyright (C) 2009 Sebastian Dröge <sebastian.droege@collabora.co.uk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <gst/check/gstcheck.h> +#include <gst/audio/audio.h> + +gboolean have_eos = FALSE; + +/* 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 */ +GstPad *mysrcpad, *mysinkpad; + +#define ECHO_CAPS_STRING \ + "audio/x-raw, " \ + "channels = (int) 2, " \ + "channel-mask = (bitmask) 3, " \ + "rate = (int) 100000, " \ + "layout = (string) interleaved, " \ + "format = (string) " GST_AUDIO_NE(F64) + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "channels = (int) [ 1, 2 ], " + "rate = (int) [ 1, MAX ], " + "format = (string) { " + GST_AUDIO_NE (F32) ", " GST_AUDIO_NE (F64) " }")); +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "channels = (int) [ 1, 2 ], " + "rate = (int) [ 1, MAX ], " + "format = (string) { " + GST_AUDIO_NE (F32) ", " GST_AUDIO_NE (F64) " }")); + +static GstElement * +setup_echo (void) +{ + GstElement *echo; + + GST_DEBUG ("setup_echo"); + echo = gst_check_setup_element ("audioecho"); + mysrcpad = gst_check_setup_src_pad (echo, &srctemplate); + mysinkpad = gst_check_setup_sink_pad (echo, &sinktemplate); + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + + return echo; +} + +static void +cleanup_echo (GstElement * echo) +{ + GST_DEBUG ("cleanup_echo"); + + g_list_foreach (buffers, (GFunc) gst_mini_object_unref, NULL); + g_list_free (buffers); + buffers = NULL; + + gst_pad_set_active (mysrcpad, FALSE); + gst_pad_set_active (mysinkpad, FALSE); + gst_check_teardown_src_pad (echo); + gst_check_teardown_sink_pad (echo); + gst_check_teardown_element (echo); +} + +GST_START_TEST (test_passthrough) +{ + GstElement *echo; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble in[] = { 1.0, -1.0, 0.0, 0.5, -0.5, 0.0 }; + gdouble res[6]; + + echo = setup_echo (); + g_object_set (G_OBJECT (echo), "delay", (GstClockTime) 1, "intensity", 0.0, + "feedback", 0.0, NULL); + fail_unless (gst_element_set_state (echo, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + caps = gst_caps_from_string (ECHO_CAPS_STRING); + gst_check_setup_events (mysrcpad, echo, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + + inbuffer = + gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY, in, sizeof (in), 0, + sizeof (in), NULL, NULL); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, sizeof (in)) == 0); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being collected on the global buffer list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + fail_unless (gst_buffer_extract (outbuffer, 0, res, + sizeof (res)) == sizeof (res)); + GST_INFO + ("expected %+lf %+lf %+lf %+lf %+lf %+lf real %+lf %+lf %+lf %+lf %+lf %+lf", + in[0], in[1], in[2], in[3], in[4], in[5], res[0], res[1], res[2], res[3], + res[4], res[5]); + fail_unless (gst_buffer_memcmp (outbuffer, 0, in, sizeof (in)) == 0); + + /* cleanup */ + cleanup_echo (echo); +} + +GST_END_TEST; + +GST_START_TEST (test_echo) +{ + GstElement *echo; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble in[] = { 1.0, -1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, }; + gdouble out[] = { 1.0, -1.0, 0.0, 0.0, 1.0, -1.0, 0.0, 0.0, 0.0, 0.0 }; + gdouble res[10]; + + echo = setup_echo (); + g_object_set (G_OBJECT (echo), "delay", (GstClockTime) 20000, "intensity", + 1.0, "feedback", 0.0, NULL); + fail_unless (gst_element_set_state (echo, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + caps = gst_caps_from_string (ECHO_CAPS_STRING); + gst_check_setup_events (mysrcpad, echo, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + + inbuffer = + gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY, in, sizeof (in), 0, + sizeof (in), NULL, NULL); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, sizeof (in)) == 0); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being collected on the global buffer list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + fail_unless (gst_buffer_extract (outbuffer, 0, res, + sizeof (res)) == sizeof (res)); + GST_INFO + ("expected %+lf %+lf %+lf %+lf %+lf %+lf %+lf %+lf %+lf %+lf real %+lf %+lf %+lf %+lf %+lf %+lf %+lf %+lf %+lf %+lf", + out[0], out[1], out[2], out[3], out[4], out[5], out[6], out[7], out[8], + out[9], res[0], res[1], res[2], res[3], res[4], res[5], res[6], res[7], + res[8], res[9]); + fail_unless (gst_buffer_memcmp (outbuffer, 0, out, sizeof (out)) == 0); + + /* cleanup */ + cleanup_echo (echo); +} + +GST_END_TEST; + +GST_START_TEST (test_feedback) +{ + GstElement *echo; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble in[] = { 1.0, -1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, }; + gdouble out[] = { 1.0, -1.0, 0.0, 0.0, 1.0, -1.0, 0.0, 0.0, 1.0, -1.0 }; + gdouble res[10]; + + echo = setup_echo (); + g_object_set (G_OBJECT (echo), "delay", (GstClockTime) 20000, "intensity", + 1.0, "feedback", 1.0, NULL); + fail_unless (gst_element_set_state (echo, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + caps = gst_caps_from_string (ECHO_CAPS_STRING); + gst_check_setup_events (mysrcpad, echo, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + + inbuffer = + gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY, in, sizeof (in), 0, + sizeof (in), NULL, NULL); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, sizeof (in)) == 0); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being collected on the global buffer list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + fail_unless (gst_buffer_extract (outbuffer, 0, res, + sizeof (res)) == sizeof (res)); + GST_INFO + ("expected %+lf %+lf %+lf %+lf %+lf %+lf %+lf %+lf %+lf %+lf real %+lf %+lf %+lf %+lf %+lf %+lf %+lf %+lf %+lf %+lf", + out[0], out[1], out[2], out[3], out[4], out[5], out[6], out[7], out[8], + out[9], res[0], res[1], res[2], res[3], res[4], res[5], res[6], res[7], + res[8], res[9]); + fail_unless (gst_buffer_memcmp (outbuffer, 0, out, sizeof (out)) == 0); + + /* cleanup */ + cleanup_echo (echo); +} + +GST_END_TEST; + +static Suite * +audioecho_suite (void) +{ + Suite *s = suite_create ("audioecho"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_passthrough); + tcase_add_test (tc_chain, test_echo); + tcase_add_test (tc_chain, test_feedback); + + return s; +} + +GST_CHECK_MAIN (audioecho); diff --git a/tests/check/elements/audiofirfilter.c b/tests/check/elements/audiofirfilter.c new file mode 100644 index 0000000..dd31e87 --- /dev/null +++ b/tests/check/elements/audiofirfilter.c @@ -0,0 +1,192 @@ +/* GStreamer + * + * Copyright (C) 2009 Sebastian Dröge <sebastian.droege@collabora.co.uk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +/* FIXME 0.11: suppress warnings for deprecated API such as GValueArray + * with newer GLib versions (>= 2.31.0) */ +#define GLIB_DISABLE_DEPRECATION_WARNINGS + +#include <gst/gst.h> +#include <gst/check/gstcheck.h> + +static gboolean have_eos = FALSE; + +static gboolean +on_message (GstBus * bus, GstMessage * message, gpointer user_data) +{ + GMainLoop *loop = (GMainLoop *) user_data; + + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ERROR: + case GST_MESSAGE_WARNING: + g_assert_not_reached (); + g_main_loop_quit (loop); + break; + + case GST_MESSAGE_EOS: + have_eos = TRUE; + g_main_loop_quit (loop); + break; + default: + break; + } + + return TRUE; +} + +static void +on_rate_changed (GstElement * element, gint rate, gpointer user_data) +{ + GValueArray *va; + GValue v = { 0, }; + + fail_unless (rate > 0); + + va = g_value_array_new (6); + + g_value_init (&v, G_TYPE_DOUBLE); + g_value_set_double (&v, 0.0); + g_value_array_append (va, &v); + g_value_reset (&v); + g_value_set_double (&v, 0.0); + g_value_array_append (va, &v); + g_value_reset (&v); + g_value_set_double (&v, 0.0); + g_value_array_append (va, &v); + g_value_reset (&v); + g_value_set_double (&v, 0.0); + g_value_array_append (va, &v); + g_value_reset (&v); + g_value_set_double (&v, 0.0); + g_value_array_append (va, &v); + g_value_reset (&v); + g_value_set_double (&v, 1.0); + g_value_array_append (va, &v); + g_value_reset (&v); + + g_object_set (G_OBJECT (element), "kernel", va, NULL); + + g_value_array_free (va); +} + +static gboolean have_data = FALSE; + +static void +on_handoff (GstElement * object, GstBuffer * buffer, GstPad * pad, + gpointer user_data) +{ + if (!have_data) { + GstMapInfo map; + gdouble *data; + + gst_buffer_map (buffer, &map, GST_MAP_READ); + data = (gdouble *) map.data; + + fail_unless (map.size > 5 * sizeof (gdouble)); + fail_unless (data[0] == 0.0); + fail_unless (data[1] == 0.0); + fail_unless (data[2] == 0.0); + fail_unless (data[3] == 0.0); + fail_unless (data[4] == 0.0); + fail_unless (data[5] != 0.0); + + gst_buffer_unmap (buffer, &map); + have_data = TRUE; + } +} + +GST_START_TEST (test_pipeline) +{ + GstElement *pipeline, *src, *cfilter, *filter, *sink; + GstCaps *caps; + GstBus *bus; + GMainLoop *loop; + + have_data = FALSE; + have_eos = FALSE; + + pipeline = gst_element_factory_make ("pipeline", NULL); + fail_unless (pipeline != NULL); + + src = gst_element_factory_make ("audiotestsrc", NULL); + fail_unless (src != NULL); + g_object_set (G_OBJECT (src), "num-buffers", 1000, NULL); + + cfilter = gst_element_factory_make ("capsfilter", NULL); + fail_unless (cfilter != NULL); +#if G_BYTE_ORDER == G_BIG_ENDIAN + caps = gst_caps_new_simple ("audio/x-raw", + "format", G_TYPE_STRING, "F64BE", NULL); +#else + caps = gst_caps_new_simple ("audio/x-raw", + "format", G_TYPE_STRING, "F64LE", NULL); +#endif + g_object_set (G_OBJECT (cfilter), "caps", caps, NULL); + gst_caps_unref (caps); + + filter = gst_element_factory_make ("audiofirfilter", NULL); + fail_unless (filter != NULL); + g_signal_connect (G_OBJECT (filter), "rate-changed", + G_CALLBACK (on_rate_changed), NULL); + + sink = gst_element_factory_make ("fakesink", NULL); + fail_unless (sink != NULL); + g_object_set (G_OBJECT (sink), "signal-handoffs", TRUE, NULL); + g_signal_connect (G_OBJECT (sink), "handoff", G_CALLBACK (on_handoff), NULL); + + gst_bin_add_many (GST_BIN (pipeline), src, cfilter, filter, sink, NULL); + fail_unless (gst_element_link_many (src, cfilter, filter, sink, NULL)); + + loop = g_main_loop_new (NULL, FALSE); + + bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); + gst_bus_add_signal_watch (bus); + g_signal_connect (G_OBJECT (bus), "message", G_CALLBACK (on_message), loop); + gst_object_unref (GST_OBJECT (bus)); + + fail_if (gst_element_set_state (pipeline, + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + g_main_loop_run (loop); + + fail_unless (have_data); + fail_unless (have_eos); + + fail_unless (gst_element_set_state (pipeline, + GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS); + + g_main_loop_unref (loop); + gst_object_unref (pipeline); +} + +GST_END_TEST; + +static Suite * +audiofirfilter_suite (void) +{ + Suite *s = suite_create ("audiofirfilter"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_pipeline); + + return s; +} + +GST_CHECK_MAIN (audiofirfilter); diff --git a/tests/check/elements/audioiirfilter.c b/tests/check/elements/audioiirfilter.c new file mode 100644 index 0000000..8faae79 --- /dev/null +++ b/tests/check/elements/audioiirfilter.c @@ -0,0 +1,189 @@ +/* GStreamer + * + * Copyright (C) 2009 Sebastian Dröge <sebastian.droege@collabora.co.uk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +/* FIXME 0.11: suppress warnings for deprecated API such as GValueArray + * with newer GLib versions (>= 2.31.0) */ +#define GLIB_DISABLE_DEPRECATION_WARNINGS + +#include <gst/gst.h> +#include <gst/check/gstcheck.h> + +static gboolean have_eos = FALSE; + +static gboolean +on_message (GstBus * bus, GstMessage * message, gpointer user_data) +{ + GMainLoop *loop = (GMainLoop *) user_data; + + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ERROR: + case GST_MESSAGE_WARNING: + g_assert_not_reached (); + g_main_loop_quit (loop); + break; + + case GST_MESSAGE_EOS: + have_eos = TRUE; + g_main_loop_quit (loop); + break; + default: + break; + } + + return TRUE; +} + +static void +on_rate_changed (GstElement * element, gint rate, gpointer user_data) +{ + GValueArray *va; + GValue v = { 0, }; + + fail_unless (rate > 0); + + va = g_value_array_new (6); + + g_value_init (&v, G_TYPE_DOUBLE); + g_value_set_double (&v, 0.0); + g_value_array_append (va, &v); + g_value_reset (&v); + g_value_set_double (&v, 0.0); + g_value_array_append (va, &v); + g_value_reset (&v); + g_value_set_double (&v, 0.0); + g_value_array_append (va, &v); + g_value_reset (&v); + g_value_set_double (&v, 0.0); + g_value_array_append (va, &v); + g_value_reset (&v); + g_value_set_double (&v, 0.0); + g_value_array_append (va, &v); + g_value_reset (&v); + g_value_set_double (&v, 1.0); + g_value_array_append (va, &v); + g_value_reset (&v); + + g_object_set (G_OBJECT (element), "b", va, NULL); + + g_value_array_free (va); + + va = g_value_array_new (6); + + g_value_set_double (&v, 1.0); + g_value_array_append (va, &v); + g_value_reset (&v); + + g_object_set (G_OBJECT (element), "a", va, NULL); + + g_value_array_free (va); +} + +static gboolean have_data = FALSE; + +static void +on_handoff (GstElement * object, GstBuffer * buffer, GstPad * pad, + gpointer user_data) +{ + if (!have_data) { + GstMapInfo map; + gfloat *data; + + fail_unless (gst_buffer_map (buffer, &map, GST_MAP_READ)); + data = (gfloat *) map.data; + + fail_unless (map.size > 5 * sizeof (gdouble)); + fail_unless (data[0] == 0.0); + fail_unless (data[1] == 0.0); + fail_unless (data[2] == 0.0); + fail_unless (data[3] == 0.0); + fail_unless (data[4] == 0.0); + fail_unless (data[5] != 0.0); + + gst_buffer_unmap (buffer, &map); + have_data = TRUE; + } +} + +GST_START_TEST (test_pipeline) +{ + GstElement *pipeline, *src, *filter, *sink; + GstBus *bus; + GMainLoop *loop; + + have_data = FALSE; + have_eos = FALSE; + + pipeline = gst_element_factory_make ("pipeline", NULL); + fail_unless (pipeline != NULL); + + src = gst_element_factory_make ("audiotestsrc", NULL); + fail_unless (src != NULL); + g_object_set (G_OBJECT (src), "num-buffers", 1000, NULL); + + filter = gst_element_factory_make ("audioiirfilter", NULL); + fail_unless (filter != NULL); + g_signal_connect (G_OBJECT (filter), "rate-changed", + G_CALLBACK (on_rate_changed), NULL); + + sink = gst_element_factory_make ("fakesink", NULL); + fail_unless (sink != NULL); + g_object_set (G_OBJECT (sink), "signal-handoffs", TRUE, NULL); + g_signal_connect (G_OBJECT (sink), "handoff", G_CALLBACK (on_handoff), NULL); + + gst_bin_add_many (GST_BIN (pipeline), src, filter, sink, NULL); + fail_unless (gst_element_link_many (src, filter, sink, NULL)); + + loop = g_main_loop_new (NULL, FALSE); + + bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); + gst_bus_add_signal_watch (bus); + g_signal_connect (G_OBJECT (bus), "message", G_CALLBACK (on_message), loop); + gst_object_unref (GST_OBJECT (bus)); + + fail_if (gst_element_set_state (pipeline, + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + g_main_loop_run (loop); + + fail_unless (have_data); + fail_unless (have_eos); + + fail_unless (gst_element_set_state (pipeline, + GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS); + + g_main_loop_unref (loop); + gst_object_unref (pipeline); +} + +GST_END_TEST; + +static Suite * +audioiirfilter_suite (void) +{ + Suite *s = suite_create ("audioiirfilter"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_pipeline); + + return s; +} + +GST_CHECK_MAIN (audioiirfilter); diff --git a/tests/check/elements/audioinvert.c b/tests/check/elements/audioinvert.c new file mode 100644 index 0000000..50981d7 --- /dev/null +++ b/tests/check/elements/audioinvert.c @@ -0,0 +1,299 @@ +/* GStreamer + * + * unit test for audioinvert + * + * Copyright (C) 2007 Sebastian Dröge <slomo@circular-chaos.org> + * + * Greatly based on the audiopanorama unit test + * Copyright (C) 2006 Stefan Kost <ensonic@users.sf.net> + * + * 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 <unistd.h> + +#include <gst/audio/audio.h> +#include <gst/base/gstbasetransform.h> +#include <gst/check/gstcheck.h> + +gboolean have_eos = FALSE; + +/* 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 */ +GstPad *mysrcpad, *mysinkpad; + + +#define INVERT_CAPS_STRING \ + "audio/x-raw, " \ + "format = (string) "GST_AUDIO_NE(S16)", " \ + "layout = (string) interleaved, " \ + "channels = (int) 1, " \ + "rate = (int) 44100" + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "format = (string) " GST_AUDIO_NE (S16) ", " + "layout = (string) interleaved, " + "channels = (int) 1, " "rate = (int) [ 1, MAX ]") + ); +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "format = (string) " GST_AUDIO_NE (S16) ", " + "layout = (string) interleaved, " + "channels = (int) 1, " "rate = (int) [ 1, MAX ]") + ); + +static GstElement * +setup_invert (void) +{ + GstElement *invert; + + GST_DEBUG ("setup_invert"); + invert = gst_check_setup_element ("audioinvert"); + mysrcpad = gst_check_setup_src_pad (invert, &srctemplate); + mysinkpad = gst_check_setup_sink_pad (invert, &sinktemplate); + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + + return invert; +} + +static void +cleanup_invert (GstElement * invert) +{ + GST_DEBUG ("cleanup_invert"); + + g_list_foreach (buffers, (GFunc) gst_mini_object_unref, NULL); + g_list_free (buffers); + buffers = NULL; + + gst_pad_set_active (mysrcpad, FALSE); + gst_pad_set_active (mysinkpad, FALSE); + gst_check_teardown_src_pad (invert); + gst_check_teardown_sink_pad (invert); + gst_check_teardown_element (invert); +} + +GST_START_TEST (test_passthrough) +{ + GstElement *invert; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gint16 in[4] = { 16384, -256, 128, -512 }; + gint16 *res; + GstMapInfo map; + + invert = setup_invert (); + fail_unless (gst_element_set_state (invert, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (8); + gst_buffer_fill (inbuffer, 0, in, 8); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 8) == 0); + caps = gst_caps_from_string (INVERT_CAPS_STRING); + gst_check_setup_events (mysrcpad, invert, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being collected on the global buffer list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gint16 *) map.data; + GST_INFO ("expected %+5d %+5d %+5d %+5d real %+5d %+5d %+5d %+5d", + in[0], in[1], in[2], in[3], res[0], res[1], res[2], res[3]); + gst_buffer_unmap (outbuffer, &map); + + fail_unless (gst_buffer_memcmp (outbuffer, 0, in, 8) == 0); + + /* cleanup */ + cleanup_invert (invert); +} + +GST_END_TEST; + +GST_START_TEST (test_zero) +{ + GstElement *invert; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gint16 in[4] = { 16384, -256, 128, -512 }; + gint16 out[4] = { 0, 0, 0, 0 }; + gint16 *res; + GstMapInfo map; + + invert = setup_invert (); + g_object_set (G_OBJECT (invert), "degree", 0.5, NULL); + fail_unless (gst_element_set_state (invert, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (8); + gst_buffer_fill (inbuffer, 0, in, 8); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 8) == 0); + caps = gst_caps_from_string (INVERT_CAPS_STRING); + gst_check_setup_events (mysrcpad, invert, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gint16 *) map.data; + GST_INFO ("expected %+5d %+5d %+5d %+5d real %+5d %+5d %+5d %+5d", + out[0], out[1], out[2], out[3], res[0], res[1], res[2], res[3]); + gst_buffer_unmap (outbuffer, &map); + + fail_unless (gst_buffer_memcmp (outbuffer, 0, out, 8) == 0); + + /* cleanup */ + cleanup_invert (invert); +} + +GST_END_TEST; + +GST_START_TEST (test_full_inverse) +{ + GstElement *invert; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gint16 in[4] = { 16384, -256, 128, -512 }; + gint16 out[4] = { -16385, 255, -129, 511 }; + gint16 *res; + GstMapInfo map; + + invert = setup_invert (); + g_object_set (G_OBJECT (invert), "degree", 1.0, NULL); + fail_unless (gst_element_set_state (invert, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (8); + gst_buffer_fill (inbuffer, 0, in, 8); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 8) == 0); + caps = gst_caps_from_string (INVERT_CAPS_STRING); + gst_check_setup_events (mysrcpad, invert, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gint16 *) map.data; + GST_INFO ("expected %+5d %+5d %+5d %+5d real %+5d %+5d %+5d %+5d", + out[0], out[1], out[2], out[3], res[0], res[1], res[2], res[3]); + gst_buffer_unmap (outbuffer, &map); + + fail_unless (gst_buffer_memcmp (outbuffer, 0, out, 8) == 0); + + /* cleanup */ + cleanup_invert (invert); +} + +GST_END_TEST; + +GST_START_TEST (test_25_inverse) +{ + GstElement *invert; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gint16 in[4] = { 16384, -256, 128, -512 }; + gint16 out[4] = { 8191, -128, 63, -256 }; + gint16 *res; + GstMapInfo map; + + invert = setup_invert (); + g_object_set (G_OBJECT (invert), "degree", 0.25, NULL); + fail_unless (gst_element_set_state (invert, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (8); + gst_buffer_fill (inbuffer, 0, in, 8); + fail_unless (gst_buffer_memcmp (inbuffer, 0, in, 8) == 0); + caps = gst_caps_from_string (INVERT_CAPS_STRING); + gst_check_setup_events (mysrcpad, invert, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... and puts a new buffer on the global list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gint16 *) map.data; + GST_INFO ("expected %+5d %+5d %+5d %+5d real %+5d %+5d %+5d %+5d", + out[0], out[1], out[2], out[3], res[0], res[1], res[2], res[3]); + gst_buffer_unmap (outbuffer, &map); + + fail_unless (gst_buffer_memcmp (outbuffer, 0, out, 8) == 0); + + /* cleanup */ + cleanup_invert (invert); +} + +GST_END_TEST; + +static Suite * +invert_suite (void) +{ + Suite *s = suite_create ("invert"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_passthrough); + tcase_add_test (tc_chain, test_zero); + tcase_add_test (tc_chain, test_full_inverse); + tcase_add_test (tc_chain, test_25_inverse); + + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = invert_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/audiopanorama.c b/tests/check/elements/audiopanorama.c new file mode 100644 index 0000000..3857119 --- /dev/null +++ b/tests/check/elements/audiopanorama.c @@ -0,0 +1,818 @@ +/* GStreamer + * + * unit test for audiopanorama + * + * Copyright (C) 2006 Stefan Kost <ensonic@users.sf.net> + * + * 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 <unistd.h> + +#include <gst/audio/audio.h> +#include <gst/base/gstbasetransform.h> +#include <gst/check/gstcheck.h> + +gboolean have_eos = FALSE; + +/* 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 */ +GstPad *mysrcpad, *mysinkpad; + + +#define PANORAMA_S16_MONO_CAPS_STRING \ + "audio/x-raw, " \ + "channels = (int) 1, " \ + "rate = (int) 44100, " \ + "layout = (string) interleaved, " \ + "format = (string) " GST_AUDIO_NE(S16) + +#define PANORAMA_S16_STEREO_CAPS_STRING \ + "audio/x-raw, " \ + "channels = (int) 2, " \ + "channel-mask = (bitmask) 3, " \ + "rate = (int) 44100, " \ + "layout = (string) interleaved, " \ + "format = (string) " GST_AUDIO_NE(S16) + +#define PANORAMA_F32_MONO_CAPS_STRING \ + "audio/x-raw, " \ + "channels = (int) 1, " \ + "rate = (int) 44100, " \ + "layout = (string) interleaved, " \ + "format = (string) " GST_AUDIO_NE(F32) + +#define PANORAMA_F32_STEREO_CAPS_STRING \ + "audio/x-raw, " \ + "channels = (int) 2, " \ + "channel-mask = (bitmask) 3, " \ + "rate = (int) 44100, " \ + "layout = (string) interleaved, " \ + "format = (string) " GST_AUDIO_NE(F32) + +#define PANORAMA_WRONG_CAPS_STRING \ + "audio/x-raw, " \ + "channels = (int) 5, " \ + "rate = (int) 44100, " \ + "layout = (string) interleaved, " \ + "format = (string) " GST_AUDIO_NE(U16) + + +static GstStaticPadTemplate sinktemplate[2] = { + GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "channels = (int) 2, " + "rate = (int) [ 1, MAX ], " + "layout = (string) interleaved, " + "format = (string) " GST_AUDIO_NE (S16)) + ), + GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "channels = (int) 2, " + "rate = (int) [ 1, MAX ], " + "layout = (string) interleaved, " + "format = (string) " GST_AUDIO_NE (F32)) + ), +}; + +static GstStaticPadTemplate msrctemplate[2] = { + GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "channels = (int) 1, " + "rate = (int) [ 1, MAX ], " + "layout = (string) interleaved, " + "format = (string) " GST_AUDIO_NE (S16)) + ), + GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "channels = (int) 1, " + "rate = (int) [ 1, MAX ], " + "layout = (string) interleaved, " + "format = (string) " GST_AUDIO_NE (F32)) + ), +}; + +static GstStaticPadTemplate ssrctemplate[2] = { + GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "channels = (int) 2, " + "rate = (int) [ 1, MAX ], " + "layout = (string) interleaved, " + "format = (string) " GST_AUDIO_NE (S16)) + ), + GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "channels = (int) 2, " + "rate = (int) [ 1, MAX ], " + "layout = (string) interleaved, " + "format = (string) " GST_AUDIO_NE (F32)) + ), +}; + +static GstElement * +setup_panorama (GstStaticPadTemplate * srctemplate, gint fmt, + const gchar * caps_str) +{ + GstElement *panorama; + + GST_DEBUG ("setup_panorama"); + panorama = gst_check_setup_element ("audiopanorama"); + mysrcpad = gst_check_setup_src_pad (panorama, &srctemplate[fmt]); + mysinkpad = gst_check_setup_sink_pad (panorama, &sinktemplate[fmt]); + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + + if (caps_str) { + GstCaps *caps = gst_caps_from_string (caps_str); + gst_check_setup_events (mysrcpad, panorama, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + } + return panorama; +} + +static GstElement * +setup_panorama_s16_m (gint method, gfloat pan) +{ + GstElement *panorama; + panorama = setup_panorama (msrctemplate, 0, PANORAMA_S16_MONO_CAPS_STRING); + g_object_set (G_OBJECT (panorama), "method", method, "panorama", pan, NULL); + gst_element_set_state (panorama, GST_STATE_PLAYING); + GST_DEBUG ("panorama(mono) ready"); + + return panorama; +} + +static GstElement * +setup_panorama_f32_m (gint method, gfloat pan) +{ + GstElement *panorama; + panorama = setup_panorama (msrctemplate, 1, PANORAMA_F32_MONO_CAPS_STRING); + g_object_set (G_OBJECT (panorama), "method", method, "panorama", pan, NULL); + gst_element_set_state (panorama, GST_STATE_PLAYING); + GST_DEBUG ("panorama(mono) ready"); + + return panorama; +} + +static GstElement * +setup_panorama_s16_s (gint method, gfloat pan) +{ + GstElement *panorama; + panorama = setup_panorama (ssrctemplate, 0, PANORAMA_S16_STEREO_CAPS_STRING); + g_object_set (G_OBJECT (panorama), "method", method, "panorama", pan, NULL); + gst_element_set_state (panorama, GST_STATE_PLAYING); + GST_DEBUG ("panorama(stereo) ready"); + + return panorama; +} + +static GstElement * +setup_panorama_f32_s (gint method, gfloat pan) +{ + GstElement *panorama; + panorama = setup_panorama (ssrctemplate, 1, PANORAMA_F32_STEREO_CAPS_STRING); + g_object_set (G_OBJECT (panorama), "method", method, "panorama", pan, NULL); + gst_element_set_state (panorama, GST_STATE_PLAYING); + GST_DEBUG ("panorama(stereo) ready"); + + return panorama; +} + +static void +cleanup_panorama (GstElement * panorama) +{ + GST_DEBUG ("cleaning up"); + gst_element_set_state (panorama, GST_STATE_NULL); + + g_list_foreach (buffers, (GFunc) gst_mini_object_unref, NULL); + g_list_free (buffers); + buffers = NULL; + + gst_pad_set_active (mysrcpad, FALSE); + gst_pad_set_active (mysinkpad, FALSE); + gst_check_teardown_src_pad (panorama); + gst_check_teardown_sink_pad (panorama); + gst_check_teardown_element (panorama); +} + +static GstBuffer * +setup_buffer (gpointer data, gsize size) +{ + return gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY, data, size, 0, + size, NULL, NULL); +} + +static void +do_panorama (gpointer in_data, gsize in_size, gpointer out_data, gsize out_size) +{ + GstBuffer *in, *out; + + in = setup_buffer (in_data, in_size); + fail_unless (gst_pad_push (mysrcpad, in) == GST_FLOW_OK); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((out = (GstBuffer *) buffers->data) == NULL); + fail_unless (gst_buffer_extract (out, 0, out_data, out_size) == out_size); +} + +/* the tests */ + +GST_START_TEST (test_ref_counts) +{ + GstElement *panorama; + GstBuffer *inbuffer, *outbuffer; + gint16 in[2] = { 16384, -256 }; + + panorama = setup_panorama (msrctemplate, 0, PANORAMA_S16_MONO_CAPS_STRING); + fail_unless (gst_element_set_state (panorama, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + inbuffer = setup_buffer (in, sizeof (in)); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being collected on the global buffer list */ + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_if (inbuffer == outbuffer); + + /* cleanup */ + fail_unless (gst_element_set_state (panorama, + GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS, "could not set to null"); + ASSERT_OBJECT_REFCOUNT (panorama, "panorama", 1); + cleanup_panorama (panorama); +} + +GST_END_TEST; + +GST_START_TEST (test_wrong_caps) +{ + GstElement *panorama; + GstBuffer *inbuffer; + gint16 in[2] = { 16384, -256 }; + GstBus *bus; + GstMessage *message; + GstCaps *caps; + + panorama = setup_panorama (msrctemplate, 0, NULL); + bus = gst_bus_new (); + gst_element_set_state (panorama, GST_STATE_PLAYING); + + inbuffer = setup_buffer (in, sizeof (in)); + gst_buffer_ref (inbuffer); + + /* set a bus here so we avoid getting state change messages */ + gst_element_set_bus (panorama, bus); + + caps = gst_caps_from_string (PANORAMA_WRONG_CAPS_STRING); + /* this actually succeeds, because the caps event is sticky */ + gst_check_setup_events (mysrcpad, panorama, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + + /* pushing gives an error because it can't negotiate with wrong caps */ + fail_unless_equals_int (gst_pad_push (mysrcpad, inbuffer), + GST_FLOW_NOT_NEGOTIATED); + /* ... and the buffer would have been lost if we didn't ref it ourselves */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + gst_buffer_unref (inbuffer); + fail_unless_equals_int (g_list_length (buffers), 0); + + /* panorama_set_caps should not have been called since basetransform caught + * the negotiation problem */ + fail_if ((message = gst_bus_pop (bus)) != NULL); + + /* cleanup */ + gst_element_set_bus (panorama, NULL); + gst_object_unref (GST_OBJECT (bus)); + gst_element_set_state (panorama, GST_STATE_NULL); + cleanup_panorama (panorama); +} + +GST_END_TEST; + +/* processing for method=psy */ + +GST_START_TEST (test_s16_mono_middle) +{ + gint16 in[2] = { 16384, -256 }; + gint16 out[4] = { 8192, 8192, -128, -128 }; + gint16 res[4]; + GstElement *panorama = setup_panorama_s16_m (0, 0.0); + + do_panorama (in, sizeof (in), res, sizeof (res)); + + GST_INFO ("exp. %+5d %+5d %+5d %+5d", out[0], out[1], out[2], out[3]); + GST_INFO ("real %+5d %+5d %+5d %+5d", res[0], res[1], res[2], res[3]); + fail_unless (memcmp (res, out, sizeof (res)) == 0); + + cleanup_panorama (panorama); +} + +GST_END_TEST; + +GST_START_TEST (test_s16_mono_left) +{ + gint16 in[2] = { 16384, -256 }; + gint16 out[4] = { 16384, 0, -256, 0 }; + gint16 res[4]; + GstElement *panorama = setup_panorama_s16_m (0, -1.0); + + do_panorama (in, sizeof (in), res, sizeof (res)); + + GST_INFO ("exp. %+5d %+5d %+5d %+5d", out[0], out[1], out[2], out[3]); + GST_INFO ("real %+5d %+5d %+5d %+5d", res[0], res[1], res[2], res[3]); + fail_unless (memcmp (res, out, sizeof (res)) == 0); + + cleanup_panorama (panorama); +} + +GST_END_TEST; + +GST_START_TEST (test_s16_mono_right) +{ + gint16 in[2] = { 16384, -256 }; + gint16 out[4] = { 0, 16384, 0, -256 }; + gint16 res[4]; + GstElement *panorama = setup_panorama_s16_m (0, 1.0); + + do_panorama (in, sizeof (in), res, sizeof (res)); + + GST_INFO ("exp. %+5d %+5d %+5d %+5d", out[0], out[1], out[2], out[3]); + GST_INFO ("real %+5d %+5d %+5d %+5d", res[0], res[1], res[2], res[3]); + fail_unless (memcmp (res, out, sizeof (res)) == 0); + + cleanup_panorama (panorama); +} + +GST_END_TEST; + +GST_START_TEST (test_s16_stereo_middle) +{ + gint16 in[4] = { 16384, -256, 8192, 128 }; + gint16 res[4]; + GstElement *panorama = setup_panorama_s16_s (0, 0.0); + + do_panorama (in, sizeof (in), res, sizeof (res)); + + GST_INFO ("exp. %+5d %+5d %+5d %+5d", in[0], in[1], in[2], in[3]); + GST_INFO ("real %+5d %+5d %+5d %+5d", res[0], res[1], res[2], res[3]); + fail_unless (memcmp (res, in, sizeof (res)) == 0); + + cleanup_panorama (panorama); +} + +GST_END_TEST; + +GST_START_TEST (test_s16_stereo_left) +{ + gint16 in[4] = { 16384, -256, 8192, 128 }; + gint16 out[4] = { 16384 - 256, 0, 8192 + 128, 0 }; + gint16 res[4]; + GstElement *panorama = setup_panorama_s16_s (0, -1.0); + + do_panorama (in, sizeof (in), res, sizeof (res)); + + GST_INFO ("exp. %+5d %+5d %+5d %+5d", out[0], out[1], out[2], out[3]); + GST_INFO ("real %+5d %+5d %+5d %+5d", res[0], res[1], res[2], res[3]); + fail_unless (memcmp (res, out, sizeof (res)) == 0); + + cleanup_panorama (panorama); +} + +GST_END_TEST; + +GST_START_TEST (test_s16_stereo_right) +{ + gint16 in[4] = { 16384, -256, 8192, 128 }; + gint16 out[4] = { 0, -256 + 16384, 0, 128 + 8192 }; + gint16 res[4]; + GstElement *panorama = setup_panorama_s16_s (0, 1.0); + + do_panorama (in, sizeof (in), res, sizeof (res)); + + GST_INFO ("exp. %+5d %+5d %+5d %+5d", out[0], out[1], out[2], out[3]); + GST_INFO ("real %+5d %+5d %+5d %+5d", res[0], res[1], res[2], res[3]); + fail_unless (memcmp (res, out, sizeof (res)) == 0); + + cleanup_panorama (panorama); +} + +GST_END_TEST; + +GST_START_TEST (test_f32_mono_middle) +{ + gfloat in[2] = { 0.5, -0.2 }; + gfloat out[4] = { 0.25, 0.25, -0.1, -0.1 }; + gfloat res[4]; + GstElement *panorama = setup_panorama_f32_m (0, 0.0); + gint i; + + do_panorama (in, sizeof (in), res, sizeof (res)); + + GST_INFO ("exp. %+4.2f %+4.2f %+4.2f %+4.2f", out[0], out[1], out[2], out[3]); + GST_INFO ("real %+4.2f %+4.2f %+4.2f %+4.2f", res[0], res[1], res[2], res[3]); + for (i = 0; i < 4; i++) + fail_unless (res[i] == out[i], "difference at pos=%d", i); + + cleanup_panorama (panorama); +} + +GST_END_TEST; + +GST_START_TEST (test_f32_mono_left) +{ + gfloat in[2] = { 0.5, -0.2 }; + gfloat out[4] = { 0.5, 0.0, -0.2, 0.0 }; + gfloat res[4]; + GstElement *panorama = setup_panorama_f32_m (0, -1.0); + gint i; + + do_panorama (in, sizeof (in), res, sizeof (res)); + + GST_INFO ("exp. %+4.2f %+4.2f %+4.2f %+4.2f", out[0], out[1], out[2], out[3]); + GST_INFO ("real %+4.2f %+4.2f %+4.2f %+4.2f", res[0], res[1], res[2], res[3]); + for (i = 0; i < 4; i++) + fail_unless (res[i] == out[i], "difference at pos=%d", i); + + cleanup_panorama (panorama); +} + +GST_END_TEST; + +GST_START_TEST (test_f32_mono_right) +{ + gfloat in[2] = { 0.5, -0.2 }; + gfloat out[4] = { 0.0, 0.5, 0.0, -0.2 }; + gfloat res[4]; + GstElement *panorama = setup_panorama_f32_m (0, 1.0); + gint i; + + do_panorama (in, sizeof (in), res, sizeof (res)); + + GST_INFO ("exp. %+4.2f %+4.2f %+4.2f %+4.2f", out[0], out[1], out[2], out[3]); + GST_INFO ("real %+4.2f %+4.2f %+4.2f %+4.2f", res[0], res[1], res[2], res[3]); + for (i = 0; i < 4; i++) + fail_unless (res[i] == out[i], "difference at pos=%d", i); + + cleanup_panorama (panorama); +} + +GST_END_TEST; + +GST_START_TEST (test_f32_stereo_middle) +{ + gfloat in[4] = { 0.5, -0.2, 0.25, 0.1 }; + gfloat res[4]; + GstElement *panorama = setup_panorama_f32_s (0, 0.0); + gint i; + + do_panorama (in, sizeof (in), res, sizeof (res)); + + GST_INFO ("exp. %+4.2f %+4.2f %+4.2f %+4.2f", in[0], in[1], in[2], in[3]); + GST_INFO ("real %+4.2f %+4.2f %+4.2f %+4.2f", res[0], res[1], res[2], res[3]); + for (i = 0; i < 4; i++) + fail_unless (res[i] == in[i], "difference at pos=%d", i); + + cleanup_panorama (panorama); +} + +GST_END_TEST; + +GST_START_TEST (test_f32_stereo_left) +{ + gfloat in[4] = { 0.5, -0.2, 0.25, 0.1 }; + gfloat out[4] = { 0.5 - 0.2, 0.0, 0.25 + 0.1, 0.0 }; + gfloat res[4]; + GstElement *panorama = setup_panorama_f32_s (0, -1.0); + gint i; + + do_panorama (in, sizeof (in), res, sizeof (res)); + + GST_INFO ("exp. %+4.2f %+4.2f %+4.2f %+4.2f", out[0], out[1], out[2], out[3]); + GST_INFO ("real %+4.2f %+4.2f %+4.2f %+4.2f", res[0], res[1], res[2], res[3]); + for (i = 0; i < 4; i++) + fail_unless (res[i] == out[i], "difference at pos=%d", i); + + cleanup_panorama (panorama); +} + +GST_END_TEST; + +GST_START_TEST (test_f32_stereo_right) +{ + gfloat in[4] = { 0.5, -0.2, 0.25, 0.1 }; + gfloat out[4] = { 0.0, -0.2 + 0.5, 0.0, 0.1 + 0.25 }; + gfloat res[4]; + GstElement *panorama = setup_panorama_f32_s (0, 1.0); + gint i; + + do_panorama (in, sizeof (in), res, sizeof (res)); + + GST_INFO ("exp. %+4.2f %+4.2f %+4.2f %+4.2f", out[0], out[1], out[2], out[3]); + GST_INFO ("real %+4.2f %+4.2f %+4.2f %+4.2f", res[0], res[1], res[2], res[3]); + for (i = 0; i < 4; i++) + fail_unless (res[i] == out[i], "difference at pos=%d", i); + + cleanup_panorama (panorama); +} + +GST_END_TEST; + +/* processing for method=simple */ + +GST_START_TEST (test_s16_mono_middle_simple) +{ + gint16 in[2] = { 16384, -256 }; + gint16 out[4] = { 16384, 16384, -256, -256 }; + gint16 res[4]; + GstElement *panorama = setup_panorama_s16_m (1, 0.0); + + do_panorama (in, sizeof (in), res, sizeof (res)); + + GST_INFO ("exp. %+5d %+5d %+5d %+5d", out[0], out[1], out[2], out[3]); + GST_INFO ("real %+5d %+5d %+5d %+5d", res[0], res[1], res[2], res[3]); + fail_unless (memcmp (res, out, sizeof (res)) == 0); + + cleanup_panorama (panorama); +} + +GST_END_TEST; + +GST_START_TEST (test_s16_mono_left_simple) +{ + gint16 in[2] = { 16384, -256 }; + gint16 out[4] = { 16384, 0, -256, 0 }; + gint16 res[4]; + GstElement *panorama = setup_panorama_s16_m (1, -1.0); + + do_panorama (in, sizeof (in), res, sizeof (res)); + + GST_INFO ("exp. %+5d %+5d %+5d %+5d", out[0], out[1], out[2], out[3]); + GST_INFO ("real %+5d %+5d %+5d %+5d", res[0], res[1], res[2], res[3]); + fail_unless (memcmp (res, out, sizeof (res)) == 0); + + cleanup_panorama (panorama); +} + +GST_END_TEST; + +GST_START_TEST (test_s16_mono_right_simple) +{ + gint16 in[2] = { 16384, -256 }; + gint16 out[4] = { 0, 16384, 0, -256 }; + gint16 res[4]; + GstElement *panorama = setup_panorama_s16_m (1, 1.0); + + do_panorama (in, sizeof (in), res, sizeof (res)); + + GST_INFO ("exp. %+5d %+5d %+5d %+5d", out[0], out[1], out[2], out[3]); + GST_INFO ("real %+5d %+5d %+5d %+5d", res[0], res[1], res[2], res[3]); + fail_unless (memcmp (res, out, sizeof (res)) == 0); + + cleanup_panorama (panorama); +} + +GST_END_TEST; + +GST_START_TEST (test_s16_stereo_middle_simple) +{ + gint16 in[4] = { 16384, -256, 8192, 128 }; + gint16 res[4]; + GstElement *panorama = setup_panorama_s16_s (1, 0.0); + + do_panorama (in, sizeof (in), res, sizeof (res)); + + GST_INFO ("exp. %+5d %+5d %+5d %+5d", in[0], in[1], in[2], in[3]); + GST_INFO ("real %+5d %+5d %+5d %+5d", res[0], res[1], res[2], res[3]); + fail_unless (memcmp (res, in, sizeof (res)) == 0); + + cleanup_panorama (panorama); +} + +GST_END_TEST; + +GST_START_TEST (test_s16_stereo_left_simple) +{ + gint16 in[4] = { 16384, -256, 8192, 128 }; + gint16 out[4] = { 16384, 0, 8192, 0 }; + gint16 res[4]; + GstElement *panorama = setup_panorama_s16_s (1, -1.0); + + do_panorama (in, sizeof (in), res, sizeof (res)); + + GST_INFO ("exp. %+5d %+5d %+5d %+5d", out[0], out[1], out[2], out[3]); + GST_INFO ("real %+5d %+5d %+5d %+5d", res[0], res[1], res[2], res[3]); + fail_unless (memcmp (res, out, sizeof (res)) == 0); + + cleanup_panorama (panorama); +} + +GST_END_TEST; + +GST_START_TEST (test_s16_stereo_right_simple) +{ + gint16 in[4] = { 16384, -256, 8192, 128 }; + gint16 out[4] = { 0, -256, 0, 128 }; + gint16 res[4]; + GstElement *panorama = setup_panorama_s16_s (1, 1.0); + + do_panorama (in, sizeof (in), res, sizeof (res)); + + GST_INFO ("exp. %+5d %+5d %+5d %+5d", out[0], out[1], out[2], out[3]); + GST_INFO ("real %+5d %+5d %+5d %+5d", res[0], res[1], res[2], res[3]); + fail_unless (memcmp (res, out, sizeof (res)) == 0); + + cleanup_panorama (panorama); +} + +GST_END_TEST; + +//----------------------------------------------------------------------------- + +GST_START_TEST (test_f32_mono_middle_simple) +{ + gfloat in[2] = { 0.5, -0.2 }; + gfloat out[4] = { 0.5, 0.5, -0.2, -0.2 }; + gfloat res[4]; + GstElement *panorama = setup_panorama_f32_m (1, 0.0); + gint i; + + do_panorama (in, sizeof (in), res, sizeof (res)); + + GST_INFO ("exp. %+4.2f %+4.2f %+4.2f %+4.2f", out[0], out[1], out[2], out[3]); + GST_INFO ("real %+4.2f %+4.2f %+4.2f %+4.2f", res[0], res[1], res[2], res[3]); + for (i = 0; i < 4; i++) + fail_unless (res[i] == out[i], "difference at pos=%d", i); + + cleanup_panorama (panorama); +} + +GST_END_TEST; + +GST_START_TEST (test_f32_mono_left_simple) +{ + gfloat in[2] = { 0.5, -0.2 }; + gfloat out[4] = { 0.5, 0.0, -0.2, 0.0 }; + gfloat res[4]; + GstElement *panorama = setup_panorama_f32_m (1, -1.0); + gint i; + + do_panorama (in, sizeof (in), res, sizeof (res)); + + GST_INFO ("exp. %+4.2f %+4.2f %+4.2f %+4.2f", out[0], out[1], out[2], out[3]); + GST_INFO ("real %+4.2f %+4.2f %+4.2f %+4.2f", res[0], res[1], res[2], res[3]); + for (i = 0; i < 4; i++) + fail_unless (res[i] == out[i], "difference at pos=%d", i); + + cleanup_panorama (panorama); +} + +GST_END_TEST; + +GST_START_TEST (test_f32_mono_right_simple) +{ + gfloat in[2] = { 0.5, -0.2 }; + gfloat out[4] = { 0.0, 0.5, 0.0, -0.2 }; + gfloat res[4]; + GstElement *panorama = setup_panorama_f32_m (1, 1.0); + gint i; + + do_panorama (in, sizeof (in), res, sizeof (res)); + + GST_INFO ("exp. %+4.2f %+4.2f %+4.2f %+4.2f", out[0], out[1], out[2], out[3]); + GST_INFO ("real %+4.2f %+4.2f %+4.2f %+4.2f", res[0], res[1], res[2], res[3]); + for (i = 0; i < 4; i++) + fail_unless (res[i] == out[i], "difference at pos=%d", i); + + cleanup_panorama (panorama); +} + +GST_END_TEST; + +GST_START_TEST (test_f32_stereo_middle_simple) +{ + gfloat in[4] = { 0.5, -0.2, 0.25, 0.1 }; + gfloat res[4]; + GstElement *panorama = setup_panorama_f32_s (1, 0.0); + gint i; + + do_panorama (in, sizeof (in), res, sizeof (res)); + + GST_INFO ("exp. %+4.2f %+4.2f %+4.2f %+4.2f", in[0], in[1], in[2], in[3]); + GST_INFO ("real %+4.2f %+4.2f %+4.2f %+4.2f", res[0], res[1], res[2], res[3]); + for (i = 0; i < 4; i++) + fail_unless (res[i] == in[i], "difference at pos=%d", i); + + cleanup_panorama (panorama); +} + +GST_END_TEST; + +GST_START_TEST (test_f32_stereo_left_simple) +{ + gfloat in[4] = { 0.5, -0.2, 0.25, 0.1 }; + gfloat out[4] = { 0.5, 0.0, 0.25, 0.0 }; + gfloat res[4]; + GstElement *panorama = setup_panorama_f32_s (1, -1.0); + gint i; + + do_panorama (in, sizeof (in), res, sizeof (res)); + + GST_INFO ("exp. %+4.2f %+4.2f %+4.2f %+4.2f", out[0], out[1], out[2], out[3]); + GST_INFO ("real %+4.2f %+4.2f %+4.2f %+4.2f", res[0], res[1], res[2], res[3]); + for (i = 0; i < 4; i++) + fail_unless (res[i] == out[i], "difference at pos=%d", i); + + cleanup_panorama (panorama); +} + +GST_END_TEST; + +GST_START_TEST (test_f32_stereo_right_simple) +{ + gfloat in[4] = { 0.5, -0.2, 0.25, 0.1 }; + gfloat out[4] = { 0.0, -0.2, 0.0, 0.1 }; + gfloat res[4]; + GstElement *panorama = setup_panorama_f32_s (1, 1.0); + gint i; + + do_panorama (in, sizeof (in), res, sizeof (res)); + + GST_INFO ("exp. %+4.2f %+4.2f %+4.2f %+4.2f", out[0], out[1], out[2], out[3]); + GST_INFO ("real %+4.2f %+4.2f %+4.2f %+4.2f", res[0], res[1], res[2], res[3]); + for (i = 0; i < 4; i++) + fail_unless (res[i] == out[i], "difference at pos=%d", i); + + cleanup_panorama (panorama); +} + +GST_END_TEST; + + +static Suite * +panorama_suite (void) +{ + Suite *s = suite_create ("panorama"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_ref_counts); + tcase_add_test (tc_chain, test_wrong_caps); + /* processing for method=psy */ + tcase_add_test (tc_chain, test_s16_mono_middle); + tcase_add_test (tc_chain, test_s16_mono_left); + tcase_add_test (tc_chain, test_s16_mono_right); + tcase_add_test (tc_chain, test_s16_stereo_middle); + tcase_add_test (tc_chain, test_s16_stereo_left); + tcase_add_test (tc_chain, test_s16_stereo_right); + tcase_add_test (tc_chain, test_f32_mono_middle); + tcase_add_test (tc_chain, test_f32_mono_left); + tcase_add_test (tc_chain, test_f32_mono_right); + tcase_add_test (tc_chain, test_f32_stereo_middle); + tcase_add_test (tc_chain, test_f32_stereo_left); + tcase_add_test (tc_chain, test_f32_stereo_right); + /* processing for method=simple */ + tcase_add_test (tc_chain, test_s16_mono_middle_simple); + tcase_add_test (tc_chain, test_s16_mono_left_simple); + tcase_add_test (tc_chain, test_s16_mono_right_simple); + tcase_add_test (tc_chain, test_s16_stereo_middle_simple); + tcase_add_test (tc_chain, test_s16_stereo_left_simple); + tcase_add_test (tc_chain, test_s16_stereo_right_simple); + tcase_add_test (tc_chain, test_f32_mono_middle_simple); + tcase_add_test (tc_chain, test_f32_mono_left_simple); + tcase_add_test (tc_chain, test_f32_mono_right_simple); + tcase_add_test (tc_chain, test_f32_stereo_middle_simple); + tcase_add_test (tc_chain, test_f32_stereo_left_simple); + tcase_add_test (tc_chain, test_f32_stereo_right_simple); + + return s; +} + +GST_CHECK_MAIN (panorama); diff --git a/tests/check/elements/audiowsincband.c b/tests/check/elements/audiowsincband.c new file mode 100644 index 0000000..0a5b9c1 --- /dev/null +++ b/tests/check/elements/audiowsincband.c @@ -0,0 +1,1152 @@ +/* GStreamer + * + * Copyright (C) 2007 Sebastian Dröge <slomo@circular-chaos.org> + * + * audiowsincband.c: Unit test for the audiowsincband element + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <gst/gst.h> +#include <gst/audio/audio.h> +#include <gst/base/gstbasetransform.h> +#include <gst/check/gstcheck.h> + +#include <math.h> + +/* 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 */ +GstPad *mysrcpad, *mysinkpad; + +#define AUDIO_WSINC_BAND_CAPS_STRING_32 \ + "audio/x-raw, " \ + "format = (string) " GST_AUDIO_NE (F32) ", " \ + "layout = (string) interleaved, " \ + "channels = (int) 1, " \ + "rate = (int) 44100" + +#define AUDIO_WSINC_BAND_CAPS_STRING_64 \ + "audio/x-raw, " \ + "format = (string) " GST_AUDIO_NE (F64) ", " \ + "layout = (string) interleaved, " \ + "channels = (int) 1, " \ + "rate = (int) 44100" + +#define FORMATS "{ "GST_AUDIO_NE (F32)","GST_AUDIO_NE (F64)" }" + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "format = (string) " FORMATS ", " + "layout = (string) interleaved, " + "channels = (int) 1, " "rate = (int) 44100") + ); +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "format = (string) " FORMATS ", " + "layout = (string) interleaved, " + "channels = (int) 1, " "rate = (int) 44100") + ); + +static GstElement * +setup_audiowsincband (void) +{ + GstElement *audiowsincband; + + GST_DEBUG ("setup_audiowsincband"); + audiowsincband = gst_check_setup_element ("audiowsincband"); + mysrcpad = gst_check_setup_src_pad (audiowsincband, &srctemplate); + mysinkpad = gst_check_setup_sink_pad (audiowsincband, &sinktemplate); + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + + return audiowsincband; +} + +static void +cleanup_audiowsincband (GstElement * audiowsincband) +{ + GST_DEBUG ("cleanup_audiowsincband"); + + g_list_foreach (buffers, (GFunc) gst_mini_object_unref, NULL); + g_list_free (buffers); + buffers = NULL; + + gst_pad_set_active (mysrcpad, FALSE); + gst_pad_set_active (mysinkpad, FALSE); + gst_check_teardown_src_pad (audiowsincband); + gst_check_teardown_sink_pad (audiowsincband); + gst_check_teardown_element (audiowsincband); +} + +/* Test if data containing only one frequency component + * at rate/2 is erased with bandpass mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_32_bp_0hz) +{ + GstElement *audiowsincband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in, *res, rms; + gint i; + GstMapInfo map; + GList *node; + GstSegment segment; + + audiowsincband = setup_audiowsincband (); + /* Set to bandpass */ + g_object_set (G_OBJECT (audiowsincband), "mode", 0, NULL); + g_object_set (G_OBJECT (audiowsincband), "length", 31, NULL); + + fail_unless (gst_element_set_state (audiowsincband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiowsincband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiowsincband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_and_alloc (1024 * sizeof (gfloat)); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 1024; i++) + in[i] = 1.0; + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (AUDIO_WSINC_BAND_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiowsincband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* ensure segment (format) properly setup */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ())); + /* ... and puts a new buffer on the global list */ + fail_unless (g_list_length (buffers) >= 1); + + for (node = buffers; node; node = node->next) { + gint buffer_length; + + fail_if ((outbuffer = (GstBuffer *) node->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + buffer_length = map.size / sizeof (gfloat); + + rms = 0.0; + for (i = 0; i < buffer_length; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / buffer_length); + fail_unless (rms <= 0.1); + + gst_buffer_unmap (outbuffer, &map); + } + + /* cleanup */ + cleanup_audiowsincband (audiowsincband); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at the band center is preserved with bandreject mode + * and a 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_32_bp_11025hz) +{ + GstElement *audiowsincband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in, *res, rms; + gint i; + GstMapInfo map; + GList *node; + GstSegment segment; + + audiowsincband = setup_audiowsincband (); + /* Set to bandpass */ + g_object_set (G_OBJECT (audiowsincband), "mode", 0, NULL); + g_object_set (G_OBJECT (audiowsincband), "length", 31, NULL); + + fail_unless (gst_element_set_state (audiowsincband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiowsincband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiowsincband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_and_alloc (1024 * sizeof (gfloat)); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 1024; i += 4) { + in[i] = 0.0; + in[i + 1] = 1.0; + in[i + 2] = 0.0; + in[i + 3] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (AUDIO_WSINC_BAND_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiowsincband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* ensure segment (format) properly setup */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ())); + /* ... and puts a new buffer on the global list */ + fail_unless (g_list_length (buffers) >= 1); + + for (node = buffers; node; node = node->next) { + gint buffer_length; + + fail_if ((outbuffer = (GstBuffer *) node->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + buffer_length = map.size / sizeof (gfloat); + rms = 0.0; + for (i = 0; i < buffer_length; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / buffer_length); + fail_unless (rms >= 0.4); + gst_buffer_unmap (outbuffer, &map); + } + + /* cleanup */ + cleanup_audiowsincband (audiowsincband); +} + +GST_END_TEST; + + +/* Test if data containing only one frequency component + * at rate/2 is erased with bandreject mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_32_bp_22050hz) +{ + GstElement *audiowsincband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in, *res, rms; + gint i; + GstMapInfo map; + GList *node; + GstSegment segment; + + audiowsincband = setup_audiowsincband (); + /* Set to bandpass */ + g_object_set (G_OBJECT (audiowsincband), "mode", 0, NULL); + g_object_set (G_OBJECT (audiowsincband), "length", 31, NULL); + + fail_unless (gst_element_set_state (audiowsincband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiowsincband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiowsincband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_and_alloc (1024 * sizeof (gfloat)); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 1024; i += 2) { + in[i] = 1.0; + in[i + 1] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (AUDIO_WSINC_BAND_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiowsincband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* ensure segment (format) properly setup */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ())); + /* ... and puts a new buffer on the global list */ + fail_unless (g_list_length (buffers) >= 1); + + for (node = buffers; node; node = node->next) { + gint buffer_length; + + fail_if ((outbuffer = (GstBuffer *) node->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + buffer_length = map.size / sizeof (gfloat); + rms = 0.0; + for (i = 0; i < buffer_length; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / buffer_length); + fail_unless (rms <= 0.3); + gst_buffer_unmap (outbuffer, &map); + } + + /* cleanup */ + cleanup_audiowsincband (audiowsincband); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at rate/2 is preserved with bandreject mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_32_br_0hz) +{ + GstElement *audiowsincband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in, *res, rms; + gint i; + GstMapInfo map; + GList *node; + GstSegment segment; + + audiowsincband = setup_audiowsincband (); + /* Set to bandreject */ + g_object_set (G_OBJECT (audiowsincband), "mode", 1, NULL); + g_object_set (G_OBJECT (audiowsincband), "length", 31, NULL); + + fail_unless (gst_element_set_state (audiowsincband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiowsincband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiowsincband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_and_alloc (1024 * sizeof (gfloat)); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 1024; i++) + in[i] = 1.0; + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (AUDIO_WSINC_BAND_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiowsincband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* ensure segment (format) properly setup */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ())); + /* ... and puts a new buffer on the global list */ + fail_unless (g_list_length (buffers) >= 1); + + for (node = buffers; node; node = node->next) { + gint buffer_length; + + fail_if ((outbuffer = (GstBuffer *) node->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + buffer_length = map.size / sizeof (gfloat); + rms = 0.0; + for (i = 0; i < buffer_length; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / buffer_length); + fail_unless (rms >= 0.9); + gst_buffer_unmap (outbuffer, &map); + } + + /* cleanup */ + cleanup_audiowsincband (audiowsincband); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at the band center is erased with bandreject mode + * and a 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_32_br_11025hz) +{ + GstElement *audiowsincband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in, *res, rms; + gint i; + GstMapInfo map; + GList *node; + GstSegment segment; + + audiowsincband = setup_audiowsincband (); + /* Set to bandreject */ + g_object_set (G_OBJECT (audiowsincband), "mode", 1, NULL); + g_object_set (G_OBJECT (audiowsincband), "length", 31, NULL); + + fail_unless (gst_element_set_state (audiowsincband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiowsincband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiowsincband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_and_alloc (1024 * sizeof (gfloat)); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + + for (i = 0; i < 1024; i += 4) { + in[i] = 0.0; + in[i + 1] = 1.0; + in[i + 2] = 0.0; + in[i + 3] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (AUDIO_WSINC_BAND_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiowsincband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* ensure segment (format) properly setup */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ())); + /* ... and puts a new buffer on the global list */ + fail_unless (g_list_length (buffers) >= 1); + + for (node = buffers; node; node = node->next) { + gint buffer_length; + + fail_if ((outbuffer = (GstBuffer *) node->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + buffer_length = map.size / sizeof (gfloat); + rms = 0.0; + for (i = 0; i < buffer_length; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / buffer_length); + fail_unless (rms <= 0.35); + gst_buffer_unmap (outbuffer, &map); + } + + /* cleanup */ + cleanup_audiowsincband (audiowsincband); +} + +GST_END_TEST; + + +/* Test if data containing only one frequency component + * at rate/2 is preserved with bandreject mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_32_br_22050hz) +{ + GstElement *audiowsincband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in, *res, rms; + gint i; + GstMapInfo map; + GList *node; + GstSegment segment; + + audiowsincband = setup_audiowsincband (); + /* Set to bandreject */ + g_object_set (G_OBJECT (audiowsincband), "mode", 1, NULL); + g_object_set (G_OBJECT (audiowsincband), "length", 31, NULL); + + fail_unless (gst_element_set_state (audiowsincband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiowsincband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiowsincband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_and_alloc (1024 * sizeof (gfloat)); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 1024; i += 2) { + in[i] = 1.0; + in[i + 1] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (AUDIO_WSINC_BAND_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiowsincband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* ensure segment (format) properly setup */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ())); + /* ... and puts a new buffer on the global list */ + fail_unless (g_list_length (buffers) >= 1); + + for (node = buffers; node; node = node->next) { + gint buffer_length; + + fail_if ((outbuffer = (GstBuffer *) node->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + buffer_length = map.size / sizeof (gfloat); + rms = 0.0; + for (i = 0; i < buffer_length; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / buffer_length); + fail_unless (rms >= 0.9); + gst_buffer_unmap (outbuffer, &map); + } + + /* cleanup */ + cleanup_audiowsincband (audiowsincband); +} + +GST_END_TEST; + +/* Test if buffers smaller than the kernel size are handled + * correctly without accessing wrong memory areas */ +GST_START_TEST (test_32_small_buffer) +{ + GstElement *audiowsincband; + GstBuffer *inbuffer; + GstCaps *caps; + gfloat *in; + gint i; + GstMapInfo map; + GstSegment segment; + + audiowsincband = setup_audiowsincband (); + /* Set to bandpass */ + g_object_set (G_OBJECT (audiowsincband), "mode", 0, NULL); + g_object_set (G_OBJECT (audiowsincband), "length", 101, NULL); + + fail_unless (gst_element_set_state (audiowsincband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiowsincband), "lower-frequency", + 44100 / 4.0 - 44100 / 16.0, NULL); + g_object_set (G_OBJECT (audiowsincband), "upper-frequency", + 44100 / 4.0 + 44100 / 16.0, NULL); + inbuffer = gst_buffer_new_and_alloc (20 * sizeof (gfloat)); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 20; i++) + in[i] = 1.0; + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (AUDIO_WSINC_BAND_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiowsincband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* ensure segment (format) properly setup */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ())); + /* ... and puts a new buffer on the global list */ + fail_unless (g_list_length (buffers) >= 1); + + /* cleanup */ + cleanup_audiowsincband (audiowsincband); +} + +GST_END_TEST; + + + + + + + + + +/* Test if data containing only one frequency component + * at rate/2 is erased with bandpass mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_64_bp_0hz) +{ + GstElement *audiowsincband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in, *res, rms; + gint i; + GstMapInfo map; + GList *node; + GstSegment segment; + + audiowsincband = setup_audiowsincband (); + /* Set to bandpass */ + g_object_set (G_OBJECT (audiowsincband), "mode", 0, NULL); + g_object_set (G_OBJECT (audiowsincband), "length", 31, NULL); + + fail_unless (gst_element_set_state (audiowsincband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiowsincband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiowsincband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_and_alloc (1024 * sizeof (gdouble)); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 1024; i++) + in[i] = 1.0; + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (AUDIO_WSINC_BAND_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiowsincband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* ensure segment (format) properly setup */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ())); + /* ... and puts a new buffer on the global list */ + fail_unless (g_list_length (buffers) >= 1); + + for (node = buffers; node; node = node->next) { + gint buffer_length; + + fail_if ((outbuffer = (GstBuffer *) node->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + buffer_length = map.size / sizeof (gdouble); + rms = 0.0; + for (i = 0; i < buffer_length; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / buffer_length); + fail_unless (rms <= 0.1); + gst_buffer_unmap (outbuffer, &map); + } + + /* cleanup */ + cleanup_audiowsincband (audiowsincband); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at the band center is preserved with bandreject mode + * and a 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_64_bp_11025hz) +{ + GstElement *audiowsincband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in, *res, rms; + gint i; + GstMapInfo map; + GList *node; + GstSegment segment; + + audiowsincband = setup_audiowsincband (); + /* Set to bandpass */ + g_object_set (G_OBJECT (audiowsincband), "mode", 0, NULL); + g_object_set (G_OBJECT (audiowsincband), "length", 31, NULL); + + fail_unless (gst_element_set_state (audiowsincband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiowsincband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiowsincband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_and_alloc (1024 * sizeof (gdouble)); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 1024; i += 4) { + in[i] = 0.0; + in[i + 1] = 1.0; + in[i + 2] = 0.0; + in[i + 3] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (AUDIO_WSINC_BAND_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiowsincband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* ensure segment (format) properly setup */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ())); + /* ... and puts a new buffer on the global list */ + fail_unless (g_list_length (buffers) >= 1); + + for (node = buffers; node; node = node->next) { + gint buffer_length; + + fail_if ((outbuffer = (GstBuffer *) node->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + buffer_length = map.size / sizeof (gdouble); + rms = 0.0; + for (i = 0; i < buffer_length; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / buffer_length); + fail_unless (rms >= 0.4); + gst_buffer_unmap (outbuffer, &map); + } + + /* cleanup */ + cleanup_audiowsincband (audiowsincband); +} + +GST_END_TEST; + + +/* Test if data containing only one frequency component + * at rate/2 is erased with bandreject mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_64_bp_22050hz) +{ + GstElement *audiowsincband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in, *res, rms; + gint i; + GstMapInfo map; + GList *node; + GstSegment segment; + + audiowsincband = setup_audiowsincband (); + /* Set to bandpass */ + g_object_set (G_OBJECT (audiowsincband), "mode", 0, NULL); + g_object_set (G_OBJECT (audiowsincband), "length", 31, NULL); + + fail_unless (gst_element_set_state (audiowsincband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiowsincband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiowsincband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_and_alloc (1024 * sizeof (gdouble)); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 1024; i += 2) { + in[i] = 1.0; + in[i + 1] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (AUDIO_WSINC_BAND_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiowsincband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* ensure segment (format) properly setup */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ())); + /* ... and puts a new buffer on the global list */ + fail_unless (g_list_length (buffers) >= 1); + + for (node = buffers; node; node = node->next) { + gint buffer_length; + + fail_if ((outbuffer = (GstBuffer *) node->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + buffer_length = map.size / sizeof (gdouble); + rms = 0.0; + for (i = 0; i < buffer_length; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / buffer_length); + fail_unless (rms <= 0.3); + gst_buffer_unmap (outbuffer, &map); + } + + /* cleanup */ + cleanup_audiowsincband (audiowsincband); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at rate/2 is preserved with bandreject mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_64_br_0hz) +{ + GstElement *audiowsincband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in, *res, rms; + gint i; + GstMapInfo map; + GList *node; + GstSegment segment; + + audiowsincband = setup_audiowsincband (); + /* Set to bandreject */ + g_object_set (G_OBJECT (audiowsincband), "mode", 1, NULL); + g_object_set (G_OBJECT (audiowsincband), "length", 31, NULL); + + fail_unless (gst_element_set_state (audiowsincband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiowsincband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiowsincband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_and_alloc (1024 * sizeof (gdouble)); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 1024; i++) + in[i] = 1.0; + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (AUDIO_WSINC_BAND_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiowsincband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* ensure segment (format) properly setup */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ())); + /* ... and puts a new buffer on the global list */ + fail_unless (g_list_length (buffers) >= 1); + + for (node = buffers; node; node = node->next) { + gint buffer_length; + + fail_if ((outbuffer = (GstBuffer *) node->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + buffer_length = map.size / sizeof (gdouble); + rms = 0.0; + for (i = 0; i < buffer_length; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / buffer_length); + fail_unless (rms >= 0.9); + gst_buffer_unmap (outbuffer, &map); + } + + /* cleanup */ + cleanup_audiowsincband (audiowsincband); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at the band center is erased with bandreject mode + * and a 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_64_br_11025hz) +{ + GstElement *audiowsincband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in, *res, rms; + gint i; + GstMapInfo map; + GList *node; + GstSegment segment; + + audiowsincband = setup_audiowsincband (); + /* Set to bandreject */ + g_object_set (G_OBJECT (audiowsincband), "mode", 1, NULL); + g_object_set (G_OBJECT (audiowsincband), "length", 31, NULL); + + fail_unless (gst_element_set_state (audiowsincband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiowsincband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiowsincband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_and_alloc (1024 * sizeof (gdouble)); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + + for (i = 0; i < 1024; i += 4) { + in[i] = 0.0; + in[i + 1] = 1.0; + in[i + 2] = 0.0; + in[i + 3] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (AUDIO_WSINC_BAND_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiowsincband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* ensure segment (format) properly setup */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ())); + /* ... and puts a new buffer on the global list */ + fail_unless (g_list_length (buffers) >= 1); + + for (node = buffers; node; node = node->next) { + gint buffer_length; + + fail_if ((outbuffer = (GstBuffer *) node->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + buffer_length = map.size / sizeof (gdouble); + rms = 0.0; + for (i = 0; i < buffer_length; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / buffer_length); + fail_unless (rms <= 0.35); + gst_buffer_unmap (outbuffer, &map); + } + + /* cleanup */ + cleanup_audiowsincband (audiowsincband); +} + +GST_END_TEST; + + +/* Test if data containing only one frequency component + * at rate/2 is preserved with bandreject mode and a + * 2000Hz frequency band around rate/4 */ +GST_START_TEST (test_64_br_22050hz) +{ + GstElement *audiowsincband; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in, *res, rms; + gint i; + GstMapInfo map; + GList *node; + GstSegment segment; + + audiowsincband = setup_audiowsincband (); + /* Set to bandreject */ + g_object_set (G_OBJECT (audiowsincband), "mode", 1, NULL); + g_object_set (G_OBJECT (audiowsincband), "length", 31, NULL); + + fail_unless (gst_element_set_state (audiowsincband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiowsincband), "lower-frequency", + 44100 / 4.0 - 1000, NULL); + g_object_set (G_OBJECT (audiowsincband), "upper-frequency", + 44100 / 4.0 + 1000, NULL); + inbuffer = gst_buffer_new_and_alloc (1024 * sizeof (gdouble)); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 1024; i += 2) { + in[i] = 1.0; + in[i + 1] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (AUDIO_WSINC_BAND_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiowsincband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* ensure segment (format) properly setup */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ())); + /* ... and puts a new buffer on the global list */ + fail_unless (g_list_length (buffers) >= 1); + + for (node = buffers; node; node = node->next) { + gint buffer_length; + + fail_if ((outbuffer = (GstBuffer *) node->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + buffer_length = map.size / sizeof (gdouble); + rms = 0.0; + for (i = 0; i < buffer_length; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / buffer_length); + fail_unless (rms >= 0.9); + gst_buffer_unmap (outbuffer, &map); + } + + /* cleanup */ + cleanup_audiowsincband (audiowsincband); +} + +GST_END_TEST; + +/* Test if buffers smaller than the kernel size are handled + * correctly without accessing wrong memory areas */ +GST_START_TEST (test_64_small_buffer) +{ + GstElement *audiowsincband; + GstBuffer *inbuffer; + GstCaps *caps; + gdouble *in; + gint i; + GstMapInfo map; + GstSegment segment; + + audiowsincband = setup_audiowsincband (); + /* Set to bandpass */ + g_object_set (G_OBJECT (audiowsincband), "mode", 0, NULL); + g_object_set (G_OBJECT (audiowsincband), "length", 101, NULL); + + fail_unless (gst_element_set_state (audiowsincband, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiowsincband), "lower-frequency", + 44100 / 4.0 - 44100 / 16.0, NULL); + g_object_set (G_OBJECT (audiowsincband), "upper-frequency", + 44100 / 4.0 + 44100 / 16.0, NULL); + inbuffer = gst_buffer_new_and_alloc (20 * sizeof (gdouble)); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 20; i++) + in[i] = 1.0; + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (AUDIO_WSINC_BAND_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiowsincband, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* ensure segment (format) properly setup */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ())); + /* ... and puts a new buffer on the global list */ + fail_unless (g_list_length (buffers) >= 1); + + /* cleanup */ + cleanup_audiowsincband (audiowsincband); +} + +GST_END_TEST; + +static Suite * +audiowsincband_suite (void) +{ + Suite *s = suite_create ("audiowsincband"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_32_bp_0hz); + tcase_add_test (tc_chain, test_32_bp_11025hz); + tcase_add_test (tc_chain, test_32_bp_22050hz); + tcase_add_test (tc_chain, test_32_br_0hz); + tcase_add_test (tc_chain, test_32_br_11025hz); + tcase_add_test (tc_chain, test_32_br_22050hz); + tcase_add_test (tc_chain, test_32_small_buffer); + tcase_add_test (tc_chain, test_64_bp_0hz); + tcase_add_test (tc_chain, test_64_bp_11025hz); + tcase_add_test (tc_chain, test_64_bp_22050hz); + tcase_add_test (tc_chain, test_64_br_0hz); + tcase_add_test (tc_chain, test_64_br_11025hz); + tcase_add_test (tc_chain, test_64_br_22050hz); + tcase_add_test (tc_chain, test_64_small_buffer); + + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = audiowsincband_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/audiowsinclimit.c b/tests/check/elements/audiowsinclimit.c new file mode 100644 index 0000000..de80451 --- /dev/null +++ b/tests/check/elements/audiowsinclimit.c @@ -0,0 +1,803 @@ +/* GStreamer + * + * Copyright (C) 2007 Sebastian Dröge <slomo@circular-chaos.org> + * + * audiowsinclimit.c: Unit test for the audiowsinclimit element + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <gst/gst.h> +#include <gst/audio/audio.h> +#include <gst/base/gstbasetransform.h> +#include <gst/check/gstcheck.h> + +#include <math.h> + +/* 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 */ +GstPad *mysrcpad, *mysinkpad; + +#define AUDIO_WSINC_LIMIT_CAPS_STRING_32 \ + "audio/x-raw, " \ + "format = (string) " GST_AUDIO_NE (F32) ", " \ + "layout = (string) interleaved, " \ + "channels = (int) 1, " \ + "rate = (int) 44100" + +#define AUDIO_WSINC_LIMIT_CAPS_STRING_64 \ + "audio/x-raw, " \ + "format = (string) " GST_AUDIO_NE (F64) ", " \ + "layout = (string) interleaved, " \ + "channels = (int) 1, " \ + "rate = (int) 44100" + +#define FORMATS "{ "GST_AUDIO_NE (F32)","GST_AUDIO_NE (F64)" }" + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "format = (string) " FORMATS ", " + "layout = (string) interleaved, " + "channels = (int) 1, " "rate = (int) 44100") + ); +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "format = (string) " FORMATS ", " + "layout = (string) interleaved, " + "channels = (int) 1, " "rate = (int) 44100") + ); + +static GstElement * +setup_audiowsinclimit (void) +{ + GstElement *audiowsinclimit; + + GST_DEBUG ("setup_audiowsinclimit"); + audiowsinclimit = gst_check_setup_element ("audiowsinclimit"); + mysrcpad = gst_check_setup_src_pad (audiowsinclimit, &srctemplate); + mysinkpad = gst_check_setup_sink_pad (audiowsinclimit, &sinktemplate); + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + + return audiowsinclimit; +} + +static void +cleanup_audiowsinclimit (GstElement * audiowsinclimit) +{ + GST_DEBUG ("cleanup_audiowsinclimit"); + + g_list_foreach (buffers, (GFunc) gst_mini_object_unref, NULL); + g_list_free (buffers); + buffers = NULL; + + gst_pad_set_active (mysrcpad, FALSE); + gst_pad_set_active (mysinkpad, FALSE); + gst_check_teardown_src_pad (audiowsinclimit); + gst_check_teardown_sink_pad (audiowsinclimit); + gst_check_teardown_element (audiowsinclimit); +} + +/* Test if data containing only one frequency component + * at 0 is preserved with lowpass mode and a cutoff + * at rate/4 */ +GST_START_TEST (test_32_lp_0hz) +{ + GstElement *audiowsinclimit; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in, *res, rms; + gint i; + GstMapInfo map; + GList *node; + GstSegment segment; + + audiowsinclimit = setup_audiowsinclimit (); + /* Set to lowpass */ + g_object_set (G_OBJECT (audiowsinclimit), "mode", 0, NULL); + g_object_set (G_OBJECT (audiowsinclimit), "length", 21, NULL); + + fail_unless (gst_element_set_state (audiowsinclimit, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + /* cutoff = sampling rate / 4, data = 0 */ + g_object_set (G_OBJECT (audiowsinclimit), "cutoff", 44100 / 4.0, NULL); + inbuffer = gst_buffer_new_and_alloc (128 * sizeof (gfloat)); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 128; i++) + in[i] = 1.0; + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (AUDIO_WSINC_LIMIT_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiowsinclimit, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* ensure segment (format) properly setup */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ())); + /* ... and puts a new buffer on the global list */ + fail_unless (g_list_length (buffers) >= 1); + + for (node = buffers; node; node = node->next) { + gint buffer_length; + + fail_if ((outbuffer = (GstBuffer *) node->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + buffer_length = map.size / sizeof (gfloat); + rms = 0.0; + for (i = 0; i < buffer_length; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / buffer_length); + gst_buffer_unmap (outbuffer, &map); + fail_unless (rms >= 0.9); + } + + /* cleanup */ + cleanup_audiowsinclimit (audiowsinclimit); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at rate/2 is erased with lowpass mode and a cutoff + * at rate/4 */ +GST_START_TEST (test_32_lp_22050hz) +{ + GstElement *audiowsinclimit; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in, *res, rms; + gint i; + GstMapInfo map; + GList *node; + GstSegment segment; + + audiowsinclimit = setup_audiowsinclimit (); + /* Set to lowpass */ + g_object_set (G_OBJECT (audiowsinclimit), "mode", 0, NULL); + g_object_set (G_OBJECT (audiowsinclimit), "length", 21, NULL); + + fail_unless (gst_element_set_state (audiowsinclimit, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiowsinclimit), "cutoff", 44100 / 4.0, NULL); + inbuffer = gst_buffer_new_and_alloc (128 * sizeof (gfloat)); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 128; i += 2) { + in[i] = 1.0; + in[i + 1] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (AUDIO_WSINC_LIMIT_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiowsinclimit, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* ensure segment (format) properly setup */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ())); + /* ... and puts a new buffer on the global list */ + fail_unless (g_list_length (buffers) >= 1); + + for (node = buffers; node; node = node->next) { + gint buffer_length; + + fail_if ((outbuffer = (GstBuffer *) node->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + buffer_length = map.size / sizeof (gfloat); + rms = 0.0; + for (i = 0; i < buffer_length; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / buffer_length); + gst_buffer_unmap (outbuffer, &map); + fail_unless (rms <= 0.1); + } + + /* cleanup */ + cleanup_audiowsinclimit (audiowsinclimit); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at 0 is erased with highpass mode and a cutoff + * at rate/4 */ +GST_START_TEST (test_32_hp_0hz) +{ + GstElement *audiowsinclimit; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in, *res, rms; + gint i; + GstMapInfo map; + GList *node; + GstSegment segment; + + audiowsinclimit = setup_audiowsinclimit (); + /* Set to highpass */ + g_object_set (G_OBJECT (audiowsinclimit), "mode", 1, NULL); + g_object_set (G_OBJECT (audiowsinclimit), "length", 21, NULL); + + fail_unless (gst_element_set_state (audiowsinclimit, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiowsinclimit), "cutoff", 44100 / 4.0, NULL); + inbuffer = gst_buffer_new_and_alloc (128 * sizeof (gfloat)); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 128; i++) + in[i] = 1.0; + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (AUDIO_WSINC_LIMIT_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiowsinclimit, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* ensure segment (format) properly setup */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ())); + /* ... and puts a new buffer on the global list */ + fail_unless (g_list_length (buffers) >= 1); + + for (node = buffers; node; node = node->next) { + gint buffer_length; + + fail_if ((outbuffer = (GstBuffer *) node->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + buffer_length = map.size / sizeof (gfloat); + rms = 0.0; + for (i = 0; i < buffer_length; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / buffer_length); + gst_buffer_unmap (outbuffer, &map); + fail_unless (rms <= 0.1); + } + + /* cleanup */ + cleanup_audiowsinclimit (audiowsinclimit); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at rate/2 is preserved with highpass mode and a cutoff + * at rate/4 */ +GST_START_TEST (test_32_hp_22050hz) +{ + GstElement *audiowsinclimit; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in, *res, rms; + gint i; + GstMapInfo map; + GList *node; + GstSegment segment; + + audiowsinclimit = setup_audiowsinclimit (); + /* Set to highpass */ + g_object_set (G_OBJECT (audiowsinclimit), "mode", 1, NULL); + g_object_set (G_OBJECT (audiowsinclimit), "length", 21, NULL); + + fail_unless (gst_element_set_state (audiowsinclimit, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiowsinclimit), "cutoff", 44100 / 4.0, NULL); + inbuffer = gst_buffer_new_and_alloc (128 * sizeof (gfloat)); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 128; i += 2) { + in[i] = 1.0; + in[i + 1] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (AUDIO_WSINC_LIMIT_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiowsinclimit, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* ensure segment (format) properly setup */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ())); + /* ... and puts a new buffer on the global list */ + fail_unless (g_list_length (buffers) >= 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + for (node = buffers; node; node = node->next) { + gint buffer_length; + + fail_if ((outbuffer = (GstBuffer *) node->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gfloat *) map.data; + buffer_length = map.size / sizeof (gfloat); + rms = 0.0; + for (i = 0; i < buffer_length; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / buffer_length); + gst_buffer_unmap (outbuffer, &map); + fail_unless (rms >= 0.9); + } + + /* cleanup */ + cleanup_audiowsinclimit (audiowsinclimit); +} + +GST_END_TEST; + +/* Test if buffers smaller than the kernel size are handled + * correctly without accessing wrong memory areas */ +GST_START_TEST (test_32_small_buffer) +{ + GstElement *audiowsinclimit; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gfloat *in; + gint i; + GstMapInfo map; + GstSegment segment; + + audiowsinclimit = setup_audiowsinclimit (); + /* Set to lowpass */ + g_object_set (G_OBJECT (audiowsinclimit), "mode", 0, NULL); + g_object_set (G_OBJECT (audiowsinclimit), "length", 101, NULL); + + fail_unless (gst_element_set_state (audiowsinclimit, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiowsinclimit), "cutoff", 44100 / 4.0, NULL); + inbuffer = gst_buffer_new_and_alloc (20 * sizeof (gfloat)); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gfloat *) map.data; + for (i = 0; i < 20; i++) + in[i] = 1.0; + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (AUDIO_WSINC_LIMIT_CAPS_STRING_32); + gst_check_setup_events (mysrcpad, audiowsinclimit, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* ensure segment (format) properly setup */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ())); + /* ... and puts a new buffer on the global list */ + fail_unless (g_list_length (buffers) >= 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + /* cleanup */ + cleanup_audiowsinclimit (audiowsinclimit); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at 0 is preserved with lowpass mode and a cutoff + * at rate/4 */ +GST_START_TEST (test_64_lp_0hz) +{ + GstElement *audiowsinclimit; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in, *res, rms; + gint i; + GstMapInfo map; + GList *node; + GstSegment segment; + + audiowsinclimit = setup_audiowsinclimit (); + /* Set to lowpass */ + g_object_set (G_OBJECT (audiowsinclimit), "mode", 0, NULL); + g_object_set (G_OBJECT (audiowsinclimit), "length", 21, NULL); + + fail_unless (gst_element_set_state (audiowsinclimit, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + /* cutoff = sampling rate / 4, data = 0 */ + g_object_set (G_OBJECT (audiowsinclimit), "cutoff", 44100 / 4.0, NULL); + inbuffer = gst_buffer_new_and_alloc (128 * sizeof (gdouble)); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 128; i++) + in[i] = 1.0; + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (AUDIO_WSINC_LIMIT_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiowsinclimit, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* ensure segment (format) properly setup */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ())); + /* ... and puts a new buffer on the global list */ + fail_unless (g_list_length (buffers) >= 1); + + for (node = buffers; node; node = node->next) { + gint buffer_length; + + fail_if ((outbuffer = (GstBuffer *) node->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + buffer_length = map.size / sizeof (gdouble); + rms = 0.0; + for (i = 0; i < buffer_length; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / buffer_length); + gst_buffer_unmap (outbuffer, &map); + fail_unless (rms >= 0.9); + } + + /* cleanup */ + cleanup_audiowsinclimit (audiowsinclimit); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at rate/2 is erased with lowpass mode and a cutoff + * at rate/4 */ +GST_START_TEST (test_64_lp_22050hz) +{ + GstElement *audiowsinclimit; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in, *res, rms; + gint i; + GstMapInfo map; + GList *node; + GstSegment segment; + + audiowsinclimit = setup_audiowsinclimit (); + /* Set to lowpass */ + g_object_set (G_OBJECT (audiowsinclimit), "mode", 0, NULL); + g_object_set (G_OBJECT (audiowsinclimit), "length", 21, NULL); + + fail_unless (gst_element_set_state (audiowsinclimit, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiowsinclimit), "cutoff", 44100 / 4.0, NULL); + inbuffer = gst_buffer_new_and_alloc (128 * sizeof (gdouble)); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 128; i += 2) { + in[i] = 1.0; + in[i + 1] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (AUDIO_WSINC_LIMIT_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiowsinclimit, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* ensure segment (format) properly setup */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ())); + /* ... and puts a new buffer on the global list */ + fail_unless (g_list_length (buffers) >= 1); + + for (node = buffers; node; node = node->next) { + gint buffer_length; + + fail_if ((outbuffer = (GstBuffer *) node->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + buffer_length = map.size / sizeof (gdouble); + rms = 0.0; + for (i = 0; i < buffer_length; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / buffer_length); + gst_buffer_unmap (outbuffer, &map); + fail_unless (rms <= 0.1); + } + + /* cleanup */ + cleanup_audiowsinclimit (audiowsinclimit); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at 0 is erased with highpass mode and a cutoff + * at rate/4 */ +GST_START_TEST (test_64_hp_0hz) +{ + GstElement *audiowsinclimit; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in, *res, rms; + gint i; + GstMapInfo map; + GList *node; + GstSegment segment; + + audiowsinclimit = setup_audiowsinclimit (); + /* Set to highpass */ + g_object_set (G_OBJECT (audiowsinclimit), "mode", 1, NULL); + g_object_set (G_OBJECT (audiowsinclimit), "length", 21, NULL); + + fail_unless (gst_element_set_state (audiowsinclimit, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiowsinclimit), "cutoff", 44100 / 4.0, NULL); + inbuffer = gst_buffer_new_and_alloc (128 * sizeof (gdouble)); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 128; i++) + in[i] = 1.0; + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (AUDIO_WSINC_LIMIT_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiowsinclimit, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* ensure segment (format) properly setup */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ())); + /* ... and puts a new buffer on the global list */ + fail_unless (g_list_length (buffers) >= 1); + + for (node = buffers; node; node = node->next) { + gint buffer_length; + + fail_if ((outbuffer = (GstBuffer *) node->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + buffer_length = map.size / sizeof (gdouble); + rms = 0.0; + for (i = 0; i < buffer_length; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / buffer_length); + gst_buffer_unmap (outbuffer, &map); + fail_unless (rms <= 0.1); + } + + /* cleanup */ + cleanup_audiowsinclimit (audiowsinclimit); +} + +GST_END_TEST; + +/* Test if data containing only one frequency component + * at rate/2 is preserved with highpass mode and a cutoff + * at rate/4 */ +GST_START_TEST (test_64_hp_22050hz) +{ + GstElement *audiowsinclimit; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in, *res, rms; + gint i; + GstMapInfo map; + GList *node; + GstSegment segment; + + audiowsinclimit = setup_audiowsinclimit (); + /* Set to highpass */ + g_object_set (G_OBJECT (audiowsinclimit), "mode", 1, NULL); + g_object_set (G_OBJECT (audiowsinclimit), "length", 21, NULL); + + fail_unless (gst_element_set_state (audiowsinclimit, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiowsinclimit), "cutoff", 44100 / 4.0, NULL); + inbuffer = gst_buffer_new_and_alloc (128 * sizeof (gdouble)); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 128; i += 2) { + in[i] = 1.0; + in[i + 1] = -1.0; + } + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (AUDIO_WSINC_LIMIT_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiowsinclimit, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* ensure segment (format) properly setup */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ())); + /* ... and puts a new buffer on the global list */ + fail_unless (g_list_length (buffers) >= 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + for (node = buffers; node; node = node->next) { + gint buffer_length; + + fail_if ((outbuffer = (GstBuffer *) node->data) == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + res = (gdouble *) map.data; + buffer_length = map.size / sizeof (gdouble); + rms = 0.0; + for (i = 0; i < buffer_length; i++) + rms += res[i] * res[i]; + rms = sqrt (rms / buffer_length); + gst_buffer_unmap (outbuffer, &map); + fail_unless (rms >= 0.9); + } + + /* cleanup */ + cleanup_audiowsinclimit (audiowsinclimit); +} + +GST_END_TEST; + +/* Test if buffers smaller than the kernel size are handled + * correctly without accessing wrong memory areas */ +GST_START_TEST (test_64_small_buffer) +{ + GstElement *audiowsinclimit; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + gdouble *in; + gint i; + GstMapInfo map; + GstSegment segment; + + audiowsinclimit = setup_audiowsinclimit (); + /* Set to lowpass */ + g_object_set (G_OBJECT (audiowsinclimit), "mode", 0, NULL); + g_object_set (G_OBJECT (audiowsinclimit), "length", 101, NULL); + + fail_unless (gst_element_set_state (audiowsinclimit, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (G_OBJECT (audiowsinclimit), "cutoff", 44100 / 4.0, NULL); + inbuffer = gst_buffer_new_and_alloc (20 * sizeof (gdouble)); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 20; i++) + in[i] = 1.0; + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (AUDIO_WSINC_LIMIT_CAPS_STRING_64); + gst_check_setup_events (mysrcpad, audiowsinclimit, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* ensure segment (format) properly setup */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ())); + /* ... and puts a new buffer on the global list */ + fail_unless (g_list_length (buffers) >= 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + /* cleanup */ + cleanup_audiowsinclimit (audiowsinclimit); +} + +GST_END_TEST; + +static Suite * +audiowsinclimit_suite (void) +{ + Suite *s = suite_create ("audiowsinclimit"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_32_lp_0hz); + tcase_add_test (tc_chain, test_32_lp_22050hz); + tcase_add_test (tc_chain, test_32_hp_0hz); + tcase_add_test (tc_chain, test_32_hp_22050hz); + tcase_add_test (tc_chain, test_32_small_buffer); + tcase_add_test (tc_chain, test_64_lp_0hz); + tcase_add_test (tc_chain, test_64_lp_22050hz); + tcase_add_test (tc_chain, test_64_hp_0hz); + tcase_add_test (tc_chain, test_64_hp_22050hz); + tcase_add_test (tc_chain, test_64_small_buffer); + + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = audiowsinclimit_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/autodetect.c b/tests/check/elements/autodetect.c new file mode 100755 index 0000000..578627c --- /dev/null +++ b/tests/check/elements/autodetect.c @@ -0,0 +1,229 @@ +/* GStreamer unit test for the autodetect elements + * + * Copyright (C) <2006> Tim-Philipp Müller <tim centricular net> + * + * 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 + +#include <gst/audio/audio.h> +#include <gst/base/gstbasesink.h> +#include <gst/check/gstcheck.h> +#include <gst/video/video.h> + +/* dummy sink elements */ + +#define GST_TYPE_FAKE_AUDIO_SINK (gst_fake_audio_sink_get_type ()) +typedef GstBaseSink GstFakeAudioSink; +typedef GstBaseSinkClass GstFakeAudioSinkClass; +GType gst_fake_audio_sink_get_type (void); +G_DEFINE_TYPE (GstFakeAudioSink, gst_fake_audio_sink, GST_TYPE_BASE_SINK); + +static void +gst_fake_audio_sink_class_init (GstFakeAudioSinkClass * klass) +{ + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); + static GstStaticPadTemplate pad_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_AUDIO_CAPS_MAKE (GST_AUDIO_FORMATS_ALL) "; ")); + + gst_element_class_set_static_metadata (gstelement_class, "Fake Audio Sink", + "Sink/Audio", "Audio sink fake for testing", "Stefan Sauer"); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&pad_template)); +} + +static void +gst_fake_audio_sink_init (GstFakeAudioSink * sink) +{ +} + +#define GST_TYPE_FAKE_VIDEO_SINK (gst_fake_video_sink_get_type ()) +typedef GstBaseSink GstFakeVideoSink; +typedef GstBaseSinkClass GstFakeVideoSinkClass; +GType gst_fake_video_sink_get_type (void); +G_DEFINE_TYPE (GstFakeVideoSink, gst_fake_video_sink, GST_TYPE_BASE_SINK); + +static void +gst_fake_video_sink_class_init (GstFakeVideoSinkClass * klass) +{ + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); + static GstStaticPadTemplate pad_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (GST_VIDEO_FORMATS_ALL) "; ")); + + gst_element_class_set_static_metadata (gstelement_class, "Fake Video Sink", + "Sink/Video", "Video sink fake for testing", "Stefan Sauer"); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&pad_template)); +} + +static void +gst_fake_video_sink_init (GstFakeVideoSink * sink) +{ +} + +static gboolean +plugin_init (GstPlugin * plugin) +{ + if (!gst_element_register (plugin, "fakeaudiosink", G_MAXINT, + GST_TYPE_FAKE_AUDIO_SINK)) + return FALSE; + if (!gst_element_register (plugin, "fakevideosink", G_MAXINT, + GST_TYPE_FAKE_VIDEO_SINK)) + return FALSE; + return TRUE; +} + +/* tests */ + +static void +test_plugs_best (GstElement * sink, GType expected) +{ + GstStateChangeReturn state_ret; + GList *child; + + state_ret = gst_element_set_state (sink, GST_STATE_READY); + fail_unless (state_ret == GST_STATE_CHANGE_SUCCESS, NULL); + + child = GST_BIN_CHILDREN (sink); + fail_unless (child != NULL, "no elements plugged"); + ck_assert_int_eq (g_list_length (child), 1); + fail_unless (G_OBJECT_TYPE (child), expected); + + /* clean up */ + gst_element_set_state (sink, GST_STATE_NULL); + gst_object_unref (sink); +} + +GST_START_TEST (test_autovideosink_plugs_best) +{ + GstElement *sink = gst_element_factory_make ("autovideosink", NULL); + + test_plugs_best (sink, GST_TYPE_FAKE_VIDEO_SINK); +} + +GST_END_TEST; + +GST_START_TEST (test_autoaudiosink_plugs_best) +{ + GstElement *sink = gst_element_factory_make ("autoaudiosink", NULL); + + test_plugs_best (sink, GST_TYPE_FAKE_AUDIO_SINK); +} + +GST_END_TEST; + +static void +test_ghostpad_error_case (GstCaps * caps, GstElement * sink, + const gchar * fake_sink_name) +{ + GstStateChangeReturn state_ret; + GstElement *pipeline, *src, *filter, *fakesink; + + pipeline = gst_pipeline_new ("pipeline"); + src = gst_element_factory_make ("fakesrc", NULL); + filter = gst_element_factory_make ("capsfilter", NULL); + + g_object_set (filter, "caps", caps, NULL); + gst_caps_unref (caps); + + gst_bin_add_many (GST_BIN (pipeline), src, filter, sink, NULL); + + fail_unless (gst_element_link (src, filter), "Failed to link src to filter"); + fail_unless (gst_element_link (filter, sink), + "Failed to link filter to sink"); + + /* this should fail, there's no such format */ + state_ret = gst_element_set_state (pipeline, GST_STATE_PAUSED); + if (state_ret == GST_STATE_CHANGE_ASYNC) { + /* make sure we wait for the actual success/failure to happen */ + GstState state; + state_ret = + gst_element_get_state (pipeline, &state, &state, GST_CLOCK_TIME_NONE); + } + fakesink = gst_bin_get_by_name (GST_BIN (sink), fake_sink_name); + if (fakesink != NULL) { + /* no real sink available */ + fail_unless (state_ret == GST_STATE_CHANGE_SUCCESS, + "pipeline _set_state() to PAUSED failed"); + gst_object_unref (fakesink); + } else { + /* autovideosink contains a real video sink */ + fail_unless (state_ret == GST_STATE_CHANGE_FAILURE, + "pipeline _set_state() to PAUSED succeeded but should have failed"); + + /* so, we hit an error and try to shut down the pipeline; this shouldn't + * deadlock or block anywhere when autovideosink resets the ghostpad + * targets etc. */ + } + state_ret = gst_element_set_state (pipeline, GST_STATE_NULL); + fail_unless (state_ret == GST_STATE_CHANGE_SUCCESS, + "State change on pipeline failed"); + + /* clean up */ + gst_object_unref (pipeline); +} + +GST_START_TEST (test_autovideosink_ghostpad_error_case) +{ + GstElement *sink = gst_element_factory_make ("autovideosink", NULL); + GstCaps *caps = gst_caps_new_simple ("video/x-raw", "format", G_TYPE_STRING, + "ABCD", NULL); + + test_ghostpad_error_case (caps, sink, "fake-video-sink"); +} + +GST_END_TEST; + +GST_START_TEST (test_autoaudiosink_ghostpad_error_case) +{ + GstElement *sink = gst_element_factory_make ("autoaudiosink", NULL); + GstCaps *caps = gst_caps_new_simple ("audio/x-raw", "format", G_TYPE_STRING, + "ABCD", NULL); + + test_ghostpad_error_case (caps, sink, "fake-audio-sink"); +} + +GST_END_TEST; + +static Suite * +autodetect_suite (void) +{ + Suite *s = suite_create ("autodetect"); + TCase *tc_chain = tcase_create ("general"); + + gst_plugin_register_static (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "autodetect-test", + "autodetect test elements", + plugin_init, + VERSION, "LGPL", PACKAGE, PACKAGE_NAME, + "http://gstreamer.freedesktop.org"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_autovideosink_plugs_best); + tcase_add_test (tc_chain, test_autoaudiosink_plugs_best); + tcase_add_test (tc_chain, test_autovideosink_ghostpad_error_case); + tcase_add_test (tc_chain, test_autoaudiosink_ghostpad_error_case); + + return s; +} + +GST_CHECK_MAIN (autodetect); diff --git a/tests/check/elements/avimux.c b/tests/check/elements/avimux.c new file mode 100644 index 0000000..47c155f --- /dev/null +++ b/tests/check/elements/avimux.c @@ -0,0 +1,284 @@ +/* GStreamer + * + * unit test for avimux + * + * Copyright (C) <2006> Mark Nauwelaerts <manauw@skynet.be> + * + * 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 <unistd.h> + +#include <gst/check/gstcheck.h> + +/* 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; + +#define AUDIO_CAPS_STRING "audio/x-ac3, " \ + "channels = (int) 1, " \ + "rate = (int) 8000" +#define VIDEO_CAPS_STRING "video/mpeg, mpegversion = (int) 4, " \ + "systemstream = (bool) false, " \ + "width = (int) 384, " \ + "height = (int) 288, " \ + "framerate = (fraction) 25/1" + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-msvideo")); +static GstStaticPadTemplate srcvideotemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (VIDEO_CAPS_STRING)); + +static GstStaticPadTemplate srcaudiotemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (AUDIO_CAPS_STRING)); + + +/* setup and teardown needs some special handling for muxer */ +static GstPad * +setup_src_pad (GstElement * element, + GstStaticPadTemplate * template, GstCaps * caps, const gchar * sinkname) +{ + GstPad *srcpad, *sinkpad; + + GST_DEBUG_OBJECT (element, "setting up sending pad"); + /* sending pad */ + srcpad = gst_pad_new_from_static_template (template, "src"); + fail_if (srcpad == NULL, "Could not create a srcpad"); + ASSERT_OBJECT_REFCOUNT (srcpad, "srcpad", 1); + + if (!(sinkpad = gst_element_get_static_pad (element, sinkname))) + sinkpad = gst_element_get_request_pad (element, sinkname); + fail_if (sinkpad == NULL, "Could not get sink pad from %s", + GST_ELEMENT_NAME (element)); + /* references are owned by: 1) us, 2) avimux, 3) collect pads */ + ASSERT_OBJECT_REFCOUNT (sinkpad, "sinkpad", 3); + if (caps) + fail_unless (gst_pad_set_caps (srcpad, caps)); + fail_unless (gst_pad_link (srcpad, sinkpad) == GST_PAD_LINK_OK, + "Could not link source and %s sink pads", GST_ELEMENT_NAME (element)); + gst_object_unref (sinkpad); /* because we got it higher up */ + + /* references are owned by: 1) avimux, 2) collect pads */ + ASSERT_OBJECT_REFCOUNT (sinkpad, "sinkpad", 2); + + return srcpad; +} + +static void +teardown_src_pad (GstElement * element, const gchar * sinkname) +{ + GstPad *srcpad, *sinkpad; + gchar *padname; + + /* clean up floating src pad */ + padname = g_strdup (sinkname); + memcpy (strchr (padname, '%'), "0", 2); + if (!(sinkpad = gst_element_get_static_pad (element, padname))) + sinkpad = gst_element_get_request_pad (element, padname); + g_free (padname); + /* pad refs held by 1) avimux 2) collectpads and 3) us (through _get) */ + ASSERT_OBJECT_REFCOUNT (sinkpad, "sinkpad", 3); + srcpad = gst_pad_get_peer (sinkpad); + + gst_pad_unlink (srcpad, sinkpad); + + /* after unlinking, pad refs still held by + * 1) avimux and 2) collectpads and 3) us (through _get) */ + ASSERT_OBJECT_REFCOUNT (sinkpad, "sinkpad", 3); + gst_object_unref (sinkpad); + /* one more ref is held by element itself */ + + /* pad refs held by both creator and this function (through _get_peer) */ + ASSERT_OBJECT_REFCOUNT (srcpad, "srcpad", 2); + gst_object_unref (srcpad); + gst_object_unref (srcpad); + +} + +static GstElement * +setup_avimux (GstStaticPadTemplate * srctemplate, const gchar * sinkname) +{ + GstElement *avimux; + + GST_DEBUG ("setup_avimux"); + avimux = gst_check_setup_element ("avimux"); + mysrcpad = setup_src_pad (avimux, srctemplate, NULL, sinkname); + mysinkpad = gst_check_setup_sink_pad (avimux, &sinktemplate); + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + + return avimux; +} + +static void +cleanup_avimux (GstElement * avimux, const gchar * sinkname) +{ + GST_DEBUG ("cleanup_avimux"); + gst_element_set_state (avimux, GST_STATE_NULL); + + gst_pad_set_active (mysrcpad, FALSE); + gst_pad_set_active (mysinkpad, FALSE); + teardown_src_pad (avimux, sinkname); + gst_check_teardown_sink_pad (avimux); + gst_check_teardown_element (avimux); +} + +static void +check_avimux_pad (GstStaticPadTemplate * srctemplate, + const gchar * src_caps_string, const gchar * chunk_id, + const gchar * sinkname) +{ + GstElement *avimux; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + int num_buffers; + int i; + guint8 data0[4] = "RIFF"; + guint8 data1[8] = "AVI LIST"; + guint8 data2[8] = "hdrlavih"; + guint8 data3[4] = "LIST"; + guint8 data4[8] = "strlstrh"; + guint8 data5[4] = "strf"; + guint8 data6[4] = "LIST"; + guint8 data7[4] = "movi"; + + avimux = setup_avimux (srctemplate, sinkname); + fail_unless (gst_element_set_state (avimux, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (1); + caps = gst_caps_from_string (src_caps_string); + gst_check_setup_events (mysrcpad, avimux, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + num_buffers = g_list_length (buffers); + /* at least expect avi header, chunk header, chunk and padding */ + fail_unless (num_buffers >= 4); + + for (i = 0; i < num_buffers; ++i) { + outbuffer = GST_BUFFER (buffers->data); + fail_if (outbuffer == NULL); + buffers = g_list_remove (buffers, outbuffer); + + switch (i) { + case 0:{ /* check riff header */ + /* avi header */ + GstMapInfo map; + gsize size; + guint8 *data; + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + data = map.data; + size = map.size; + + fail_unless (memcmp (data, data0, sizeof (data0)) == 0); + fail_unless (memcmp (data + 8, data1, sizeof (data1)) == 0); + fail_unless (memcmp (data + 20, data2, sizeof (data2)) == 0); + /* video or audio header */ + data += 32 + 56; + fail_unless (memcmp (data, data3, sizeof (data3)) == 0); + fail_unless (memcmp (data + 8, data4, sizeof (data4)) == 0); + fail_unless (memcmp (data + 76, data5, sizeof (data5)) == 0); + /* avi data header */ + data = map.data; + data += size - 12; + fail_unless (memcmp (data, data6, sizeof (data6)) == 0); + data += 8; + fail_unless (memcmp (data, data7, sizeof (data7)) == 0); + gst_buffer_unmap (outbuffer, &map); + break; + } + case 1: /* chunk header */ + fail_unless (gst_buffer_get_size (outbuffer) == 8); + fail_unless (gst_buffer_memcmp (outbuffer, 0, chunk_id, 4) == 0); + break; + case 2: + fail_unless (gst_buffer_get_size (outbuffer) == 1); + break; + case 3: /* buffer we put in, must be padded to even size */ + fail_unless (gst_buffer_get_size (outbuffer) == 1); + break; + default: + break; + } + + ASSERT_BUFFER_REFCOUNT (outbuffer, "outbuffer", 1); + gst_buffer_unref (outbuffer); + outbuffer = NULL; + } + + cleanup_avimux (avimux, sinkname); + g_list_free (buffers); + buffers = NULL; +} + + +GST_START_TEST (test_video_pad) +{ + check_avimux_pad (&srcvideotemplate, VIDEO_CAPS_STRING, "00db", "video_%u"); +} + +GST_END_TEST; + + +GST_START_TEST (test_audio_pad) +{ + check_avimux_pad (&srcaudiotemplate, AUDIO_CAPS_STRING, "00wb", "audio_%u"); +} + +GST_END_TEST; + + +static Suite * +avimux_suite (void) +{ + Suite *s = suite_create ("avimux"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_video_pad); + tcase_add_test (tc_chain, test_audio_pad); + + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = avimux_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/avisubtitle.c b/tests/check/elements/avisubtitle.c new file mode 100644 index 0000000..1b00b15 --- /dev/null +++ b/tests/check/elements/avisubtitle.c @@ -0,0 +1,263 @@ +/* GStreamer + * + * unit test for avisubtitle + * + * Copyright (C) <2007> Thijs Vermeir <thijsvermeir@gmail.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. + */ +/* Element-Checklist-Version: 5 */ + +#include <unistd.h> + +#include <gst/gst.h> +#include <gst/check/gstcheck.h> + +GstPad *mysinkpad; +GstPad *mysrcpad; + +guint8 avisub_utf_8_with_bom[] = { + 0x47, 0x41, 0x42, 0x32, 0x00, 0x02, 0x00, 0x10, + 0x00, 0x00, 0x00, 0x45, 0x00, 0x6e, 0x00, 0x67, + 0x00, 0x6c, 0x00, 0x69, 0x00, 0x73, 0x00, 0x68, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x8e, 0x00, 0x00, + 0x00, 0xef, 0xbb, 0xbf, 0x31, 0x0d, 0x0a, 0x30, + 0x30, 0x3a, 0x30, 0x30, 0x3a, 0x30, 0x30, 0x2c, + 0x31, 0x30, 0x30, 0x20, 0x2d, 0x2d, 0x3e, 0x20, + 0x30, 0x30, 0x3a, 0x30, 0x30, 0x3a, 0x30, 0x32, + 0x2c, 0x30, 0x30, 0x30, 0x0d, 0x0a, 0x3c, 0x62, + 0x3e, 0x41, 0x6e, 0x20, 0x55, 0x54, 0x46, 0x38, + 0x20, 0x53, 0x75, 0x62, 0x74, 0x69, 0x74, 0x6c, + 0x65, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x42, + 0x4f, 0x4d, 0x3c, 0x2f, 0x62, 0x3e, 0x0d, 0x0a, + 0x0d, 0x0a, 0x32, 0x0d, 0x0a, 0x30, 0x30, 0x3a, + 0x30, 0x30, 0x3a, 0x30, 0x32, 0x2c, 0x31, 0x30, + 0x30, 0x20, 0x2d, 0x2d, 0x3e, 0x20, 0x30, 0x30, + 0x3a, 0x30, 0x30, 0x3a, 0x30, 0x34, 0x2c, 0x30, + 0x30, 0x30, 0x0d, 0x0a, 0x53, 0x6f, 0x6d, 0x65, + 0x74, 0x68, 0x69, 0x6e, 0x67, 0x20, 0x6e, 0x6f, + 0x6e, 0x41, 0x53, 0x43, 0x49, 0x49, 0x20, 0x2d, + 0x20, 0xc2, 0xb5, 0xc3, 0xb6, 0xc3, 0xa4, 0xc3, + 0xbc, 0xc3, 0x9f, 0x0d, 0x0a, 0x0d, 0x0a +}; + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-subtitle") + ); + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-subtitle-avi") + ); + +static GstElement * +setup_avisubtitle (void) +{ + GstElement *avisubtitle; + GstCaps *srccaps; + + GST_DEBUG ("setup_avisubtitle"); + avisubtitle = gst_check_setup_element ("avisubtitle"); + mysinkpad = gst_check_setup_sink_pad (avisubtitle, &sink_template); + mysrcpad = gst_check_setup_src_pad (avisubtitle, &src_template); + gst_pad_set_active (mysinkpad, TRUE); + gst_pad_set_active (mysrcpad, TRUE); + srccaps = gst_caps_new_empty_simple ("application/x-subtitle-avi"); + gst_check_setup_events (mysrcpad, avisubtitle, srccaps, GST_FORMAT_TIME); + gst_caps_unref (srccaps); + return avisubtitle; +} + +static void +cleanup_avisubtitle (GstElement * avisubtitle) +{ + gst_pad_set_active (mysinkpad, FALSE); + gst_pad_set_active (mysrcpad, FALSE); + gst_check_teardown_sink_pad (avisubtitle); + gst_check_teardown_src_pad (avisubtitle); + gst_check_teardown_element (avisubtitle); +} + +static void +check_wrong_buffer (guint8 * data, guint length) +{ + GstBuffer *buffer = gst_buffer_new_allocate (NULL, length, 0); + GstElement *avisubtitle = setup_avisubtitle (); + + gst_buffer_fill (buffer, 0, data, length); + fail_unless (gst_element_set_state (avisubtitle, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + gst_buffer_ref (buffer); + ASSERT_BUFFER_REFCOUNT (buffer, "inbuffer", 2); + /* push the broken buffer */ + fail_unless (gst_pad_push (mysrcpad, buffer) == GST_FLOW_ERROR, + "accepted a broken buffer"); + /* check if we have unreffed this buffer on failure */ + ASSERT_BUFFER_REFCOUNT (buffer, "inbuffer", 1); + gst_buffer_unref (buffer); + fail_unless (gst_element_set_state (avisubtitle, + GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS, "could not set to null"); + cleanup_avisubtitle (avisubtitle); +} + +static void +check_correct_buffer (guint8 * src_data, guint src_size, guint8 * dst_data, + guint dst_size) +{ + GstBuffer *buffer = gst_buffer_new_allocate (NULL, src_size, 0); + GstBuffer *newBuffer; + GstElement *avisubtitle = setup_avisubtitle (); + GstEvent *event; + + fail_unless (g_list_length (buffers) == 0, "Buffers list needs to be empty"); + gst_buffer_fill (buffer, 0, src_data, src_size); + fail_unless (gst_element_set_state (avisubtitle, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + ASSERT_BUFFER_REFCOUNT (buffer, "inbuffer", 1); + event = gst_event_new_seek (1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, + GST_SEEK_TYPE_SET, 2 * GST_SECOND, GST_SEEK_TYPE_SET, 5 * GST_SECOND); + fail_unless (gst_element_send_event (avisubtitle, event) == FALSE, + "Seeking is not possible when there is no buffer yet"); + fail_unless (gst_pad_push (mysrcpad, buffer) == GST_FLOW_OK, + "not accepted a correct buffer"); + /* we gave away our reference to the buffer, don't assume anything */ + buffer = NULL; + /* a new buffer is created in the list */ + fail_unless (g_list_length (buffers) == 1, + "No new buffer in the buffers list"); + event = gst_event_new_seek (1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, + GST_SEEK_TYPE_SET, 2 * GST_SECOND, GST_SEEK_TYPE_SET, 5 * GST_SECOND); + fail_unless (gst_element_send_event (avisubtitle, event) == TRUE, + "seeking should be working now"); + fail_unless (g_list_length (buffers) == 2, + "After seeking we need another buffer in the buffers"); + newBuffer = GST_BUFFER (buffers->data); + buffers = g_list_remove (buffers, newBuffer); + fail_unless (g_list_length (buffers) == 1, "Buffers list needs to be empty"); + fail_unless (gst_buffer_get_size (newBuffer) == dst_size, + "size of the new buffer is wrong ( %d != %d)", + gst_buffer_get_size (newBuffer), dst_size); + fail_unless (gst_buffer_memcmp (newBuffer, 0, dst_data, dst_size) == 0, + "data of the buffer is not correct"); + gst_buffer_unref (newBuffer); + /* free the buffer from seeking */ + gst_buffer_unref (GST_BUFFER (buffers->data)); + buffers = g_list_remove (buffers, buffers->data); + fail_unless (gst_element_set_state (avisubtitle, + GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS, "could not set to null"); + cleanup_avisubtitle (avisubtitle); +} + + +GST_START_TEST (test_avisubtitle_negative) +{ + guint8 wrong_magic[] = + { 0x47, 0x41, 0x41, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00 + }; + guint8 wrong_fixed_word_2[] = { + 0x47, 0x41, 0x42, 0x32, 0x00, 0x02, 0x01, 0x10, + 0x00, 0x00, 0x00, 0x45, 0x00, 0x6e, 0x00, 0x67, + 0x00, 0x6c, 0x00, 0x69, 0x00, 0x73, 0x00, 0x68 + }; + guint8 wrong_length_after_name[] = { + 0x47, 0x41, 0x42, 0x32, 0x00, 0x02, 0x00, 0x10, + 0x00, 0x00, 0x00, 0x45, 0x00, 0x6e, 0x00, 0x67, + 0x00, 0x6c, 0x00, 0x69, 0x00, 0x73, 0x00, 0x68 + }; + guint8 wrong_fixed_word_4[] = { + 0x47, 0x41, 0x42, 0x32, 0x00, 0x02, 0x00, 0x10, + 0x00, 0x00, 0x00, 0x45, 0x00, 0x6e, 0x00, 0x67, + 0x00, 0x6c, 0x00, 0x69, 0x00, 0x73, 0x00, 0x68, + 0x00, 0x00, 0x00, 0x04, 0x01, 0x8e, 0x00, 0x00, + 0x00, 0xef, 0xbb, 0xbf, 0x31, 0x0d, 0x0a, 0x30 + }; + guint8 wrong_total_length[] = { + 0x47, 0x41, 0x42, 0x32, 0x00, 0x02, 0x00, 0x10, + 0x00, 0x00, 0x00, 0x45, 0x00, 0x6e, 0x00, 0x67, + 0x00, 0x6c, 0x00, 0x69, 0x00, 0x73, 0x00, 0x68, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x8e, 0x00, 0x00, + 0x00, 0xef, 0xbb, 0xbf, 0x31, 0x0d, 0x0a, 0x30 + }; + /* size of the buffer must be larger than 11 */ + check_wrong_buffer (avisub_utf_8_with_bom, 11); + /* buffer must start with 'GAB2\0' */ + check_wrong_buffer (wrong_magic, 14); + /* next word must be 2 */ + check_wrong_buffer (wrong_fixed_word_2, 24); + /* length must be larger than the length of the name + 17 */ + check_wrong_buffer (wrong_length_after_name, 24); + /* next word must be 4 */ + check_wrong_buffer (wrong_fixed_word_4, 36); + /* check wrong total length */ + check_wrong_buffer (wrong_total_length, 36); +} + +GST_END_TEST; + +GST_START_TEST (test_avisubtitle_positive) +{ + guint8 avisub_utf_8_without_bom[] = { + 0x47, 0x41, 0x42, 0x32, 0x00, 0x02, 0x00, 0x10, + 0x00, 0x00, 0x00, 0x45, 0x00, 0x6e, 0x00, 0x67, + 0x00, 0x6c, 0x00, 0x69, 0x00, 0x73, 0x00, 0x68, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x8b, 0x00, 0x00, + 0x00, 0x31, 0x0d, 0x0a, 0x30, + 0x30, 0x3a, 0x30, 0x30, 0x3a, 0x30, 0x30, 0x2c, + 0x31, 0x30, 0x30, 0x20, 0x2d, 0x2d, 0x3e, 0x20, + 0x30, 0x30, 0x3a, 0x30, 0x30, 0x3a, 0x30, 0x32, + 0x2c, 0x30, 0x30, 0x30, 0x0d, 0x0a, 0x3c, 0x62, + 0x3e, 0x41, 0x6e, 0x20, 0x55, 0x54, 0x46, 0x38, + 0x20, 0x53, 0x75, 0x62, 0x74, 0x69, 0x74, 0x6c, + 0x65, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x42, + 0x4f, 0x4d, 0x3c, 0x2f, 0x62, 0x3e, 0x0d, 0x0a, + 0x0d, 0x0a, 0x32, 0x0d, 0x0a, 0x30, 0x30, 0x3a, + 0x30, 0x30, 0x3a, 0x30, 0x32, 0x2c, 0x31, 0x30, + 0x30, 0x20, 0x2d, 0x2d, 0x3e, 0x20, 0x30, 0x30, + 0x3a, 0x30, 0x30, 0x3a, 0x30, 0x34, 0x2c, 0x30, + 0x30, 0x30, 0x0d, 0x0a, 0x53, 0x6f, 0x6d, 0x65, + 0x74, 0x68, 0x69, 0x6e, 0x67, 0x20, 0x6e, 0x6f, + 0x6e, 0x41, 0x53, 0x43, 0x49, 0x49, 0x20, 0x2d, + 0x20, 0xc2, 0xb5, 0xc3, 0xb6, 0xc3, 0xa4, 0xc3, + 0xbc, 0xc3, 0x9f, 0x0d, 0x0a, 0x0d, 0x0a + }; + check_correct_buffer (avisub_utf_8_with_bom, 175, avisub_utf_8_with_bom + 36, + 139); + check_correct_buffer (avisub_utf_8_without_bom, 172, + avisub_utf_8_without_bom + 33, 139); +} + +GST_END_TEST; + +static Suite * +avisubtitle_suite (void) +{ + Suite *s = suite_create ("avisubtitle"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_avisubtitle_negative); + tcase_add_test (tc_chain, test_avisubtitle_positive); + + return s; +} + +GST_CHECK_MAIN (avisubtitle); diff --git a/tests/check/elements/capssetter.c b/tests/check/elements/capssetter.c new file mode 100644 index 0000000..17e563b --- /dev/null +++ b/tests/check/elements/capssetter.c @@ -0,0 +1,231 @@ +/* GStreamer + * + * unit test for capssetter + * + * Copyright (C) <2009> Mark Nauwelaerts <mnauw@users.sourceforge.net> + * + * 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 <unistd.h> + +#include <gst/check/gstcheck.h> + + +/* 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; + + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static GstElement * +setup_capssetter (void) +{ + GstElement *capssetter; + + GST_DEBUG ("setup_capssetter"); + + capssetter = gst_check_setup_element ("capssetter"); + mysrcpad = gst_check_setup_src_pad (capssetter, &srctemplate); + mysinkpad = gst_check_setup_sink_pad (capssetter, &sinktemplate); + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + + return capssetter; +} + +static void +cleanup_capssetter (GstElement * capssetter) +{ + GST_DEBUG ("cleanup_capssetter"); + + gst_pad_set_active (mysrcpad, FALSE); + gst_pad_set_active (mysinkpad, FALSE); + gst_check_teardown_src_pad (capssetter); + gst_check_teardown_sink_pad (capssetter); + gst_check_teardown_element (capssetter); +} + +static void +push_and_test (GstCaps * prop_caps, gboolean join, gboolean replace, + GstCaps * in_caps, GstCaps * out_caps) +{ + GstElement *capssetter; + GstBuffer *buffer; + GstCaps *current_out; + + capssetter = setup_capssetter (); + fail_unless (gst_element_set_state (capssetter, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + g_object_set (capssetter, "join", join, NULL); + g_object_set (capssetter, "replace", replace, NULL); + g_object_set (capssetter, "caps", prop_caps, NULL); + gst_caps_unref (prop_caps); + + buffer = gst_buffer_new_and_alloc (4); + ASSERT_BUFFER_REFCOUNT (buffer, "buffer", 1); + gst_buffer_fill (buffer, 0, "data", 4); + + gst_check_setup_events (mysrcpad, capssetter, in_caps, GST_FORMAT_TIME); + gst_caps_unref (in_caps); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, buffer) == GST_FLOW_OK, + "Failed pushing buffer to capssetter"); + + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ()) == TRUE); + + /* ... but it should end up being collected on the global buffer list */ + fail_unless (g_list_length (buffers) == 1); + buffer = g_list_first (buffers)->data; + ASSERT_BUFFER_REFCOUNT (buffer, "buffer", 1); + + current_out = gst_pad_get_current_caps (mysinkpad); + fail_unless (gst_caps_is_equal (out_caps, current_out)); + gst_caps_unref (current_out); + gst_caps_unref (out_caps); + + /* cleanup */ + cleanup_capssetter (capssetter); +} + +#define SRC_WIDTH 8 +#define SRC_HEIGHT 12 + +static GstCaps * +make_src_caps (void) +{ + return gst_caps_new_simple ("video/x-foo", "width", G_TYPE_INT, SRC_WIDTH, + "height", G_TYPE_INT, SRC_HEIGHT, NULL); +} + +/* don't try these caps mutations at home, folks */ + +GST_START_TEST (test_n_join_n_replace) +{ + GstCaps *in_caps, *prop_caps, *out_caps; + + in_caps = make_src_caps (); + prop_caps = gst_caps_new_simple ("video/x-bar", + "width", G_TYPE_INT, 2 * SRC_WIDTH, NULL); + out_caps = gst_caps_new_simple ("video/x-bar", + "width", G_TYPE_INT, 2 * SRC_WIDTH, + "height", G_TYPE_INT, SRC_HEIGHT, NULL); + push_and_test (prop_caps, FALSE, FALSE, in_caps, out_caps); +} + +GST_END_TEST; + +GST_START_TEST (test_n_join_replace) +{ + GstCaps *in_caps, *prop_caps, *out_caps; + + in_caps = make_src_caps (); + prop_caps = gst_caps_new_simple ("video/x-bar", + "width", G_TYPE_INT, 2 * SRC_WIDTH, NULL); + out_caps = gst_caps_copy (prop_caps); + push_and_test (prop_caps, FALSE, TRUE, in_caps, out_caps); +} + +GST_END_TEST; + +GST_START_TEST (test_join_n_replace_n_match) +{ + GstCaps *in_caps, *prop_caps, *out_caps; + + /* non joining caps */ + in_caps = make_src_caps (); + prop_caps = gst_caps_new_simple ("video/x-bar", + "width", G_TYPE_INT, 2 * SRC_WIDTH, NULL); + out_caps = gst_caps_copy (in_caps); + push_and_test (prop_caps, TRUE, FALSE, in_caps, out_caps); +} + +GST_END_TEST; + +GST_START_TEST (test_join_n_replace_match) +{ + GstCaps *in_caps, *prop_caps, *out_caps; + + /* joining caps */ + in_caps = make_src_caps (); + prop_caps = gst_caps_new_simple ("video/x-foo", + "width", G_TYPE_INT, 2 * SRC_WIDTH, NULL); + out_caps = gst_caps_new_simple ("video/x-foo", + "width", G_TYPE_INT, 2 * SRC_WIDTH, + "height", G_TYPE_INT, SRC_HEIGHT, NULL); + push_and_test (prop_caps, TRUE, FALSE, in_caps, out_caps); +} + +GST_END_TEST; + +GST_START_TEST (test_join_replace_n_match) +{ + GstCaps *in_caps, *prop_caps, *out_caps; + + /* non joining caps */ + in_caps = make_src_caps (); + prop_caps = gst_caps_new_simple ("video/x-bar", + "width", G_TYPE_INT, 2 * SRC_WIDTH, NULL); + out_caps = gst_caps_copy (in_caps); + push_and_test (prop_caps, TRUE, TRUE, in_caps, out_caps); +} + +GST_END_TEST; + +GST_START_TEST (test_join_replace_match) +{ + GstCaps *in_caps, *prop_caps, *out_caps; + + /* joining caps */ + in_caps = make_src_caps (); + prop_caps = gst_caps_new_simple ("video/x-foo", + "width", G_TYPE_INT, 2 * SRC_WIDTH, NULL); + out_caps = gst_caps_copy (prop_caps); + push_and_test (prop_caps, TRUE, TRUE, in_caps, out_caps); +} + +GST_END_TEST; + +static Suite * +capssetter_suite (void) +{ + Suite *s = suite_create ("capssetter"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_n_join_n_replace); + tcase_add_test (tc_chain, test_n_join_replace); + tcase_add_test (tc_chain, test_join_n_replace_n_match); + tcase_add_test (tc_chain, test_join_n_replace_match); + tcase_add_test (tc_chain, test_join_replace_n_match); + tcase_add_test (tc_chain, test_join_replace_match); + + return s; +} + +GST_CHECK_MAIN (capssetter); diff --git a/tests/check/elements/deinterlace.c b/tests/check/elements/deinterlace.c new file mode 100644 index 0000000..c9da2dd --- /dev/null +++ b/tests/check/elements/deinterlace.c @@ -0,0 +1,463 @@ +/* GStreamer unit tests for the deinterlace element + * Copyright (C) 2010 Thiago Santos <thiago.sousa.santos@collabora.co.uk> + * + * 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 + +#include <stdio.h> +#include <gst/check/gstcheck.h> +#include <gst/video/video.h> + +static gboolean +gst_caps_is_interlaced (GstCaps * caps) +{ + GstVideoInfo info; + + fail_unless (gst_caps_is_fixed (caps)); + fail_unless (gst_video_info_from_caps (&info, caps)); + + return GST_VIDEO_INFO_IS_INTERLACED (&info); +} + +GST_START_TEST (test_create_and_unref) +{ + GstElement *deinterlace; + + deinterlace = gst_element_factory_make ("deinterlace", NULL); + fail_unless (deinterlace != NULL); + + gst_element_set_state (deinterlace, GST_STATE_NULL); + gst_object_unref (deinterlace); +} + +GST_END_TEST; + +#define CAPS_VIDEO_COMMON \ + "width=(int)800, height=(int)600, framerate=(fraction)15/1" + +#define CAPS_IMAGE_COMMON \ + "width=(int)3200, height=(int)3400, framerate=(fraction)0/1" + +#define CAPS_YUY2 \ + "video/x-raw, " \ + CAPS_VIDEO_COMMON ", " \ + "format=(string)YUY2" + +#define CAPS_YUY2_INTERLACED \ + CAPS_YUY2 ", " \ + "interlace-mode=interleaved" + +#define CAPS_YVYU \ + "video/x-raw, " \ + CAPS_VIDEO_COMMON ", " \ + "format=(string)YVYU" + +#define CAPS_YVYU_INTERLACED \ + CAPS_YVYU ", " \ + "interlace-mode=interleaved" + +#define CAPS_YUY2_IMAGE \ + "video/x-raw, " \ + CAPS_IMAGE_COMMON ", " \ + "format=(string)YUY2" + +#define CAPS_YUY2_INTERLACED_IMAGE \ + CAPS_YUY2_IMAGE ", " \ + "interlace-mode=interleaved" + +#define CAPS_YVYU_IMAGE \ + "video/x-raw, " \ + CAPS_IMAGE_COMMON ", " \ + "format=(string)YVYU" + +#define CAPS_YVYU_INTERLACED_IMAGE \ + CAPS_YVYU_IMAGE ", " \ + "interlace-mode=interleaved" + +static GstElement *deinterlace; +static GstPad *srcpad; +static GstPad *sinkpad; + +static GstElement *pipeline; + +/* sets up deinterlace and shortcut pointers to its pads */ +static void +setup_deinterlace (void) +{ + deinterlace = gst_element_factory_make ("deinterlace", NULL); + fail_unless (deinterlace != NULL); + + sinkpad = gst_element_get_static_pad (deinterlace, "sink"); + fail_unless (sinkpad != NULL); + srcpad = gst_element_get_static_pad (deinterlace, "src"); + fail_unless (srcpad != NULL); +} + +/* sets up a basic test pipeline containing: + * + * videotestsrc ! capsfilter ! deinterlace ! fakesink + * + * The parameters set the capsfilter caps and the num-buffers + * property of videotestsrc + * + * It is useful for adding buffer probes to deinterlace pads + * and validating inputs/outputs + */ +static void +setup_test_pipeline (gint mode, GstCaps * infiltercaps, GstCaps * outfiltercaps, + gint numbuffers) +{ + GstElement *src; + GstElement *infilter; + GstElement *outfilter; + GstElement *sink; + + setup_deinterlace (); + + pipeline = gst_pipeline_new ("pipeline"); + src = gst_element_factory_make ("videotestsrc", NULL); + infilter = gst_element_factory_make ("capsfilter", "infilter"); + outfilter = gst_element_factory_make ("capsfilter", "outfilter"); + sink = gst_element_factory_make ("fakesink", NULL); + fail_if (src == NULL); + fail_if (infilter == NULL); + fail_if (outfilter == NULL); + fail_if (sink == NULL); + + fail_unless (gst_bin_add (GST_BIN (pipeline), src)); + fail_unless (gst_bin_add (GST_BIN (pipeline), infilter)); + fail_unless (gst_bin_add (GST_BIN (pipeline), deinterlace)); + fail_unless (gst_bin_add (GST_BIN (pipeline), outfilter)); + fail_unless (gst_bin_add (GST_BIN (pipeline), sink)); + + /* set the properties */ + g_object_set (deinterlace, "mode", mode, NULL); + if (numbuffers > 0) + g_object_set (src, "num-buffers", numbuffers, NULL); + if (infiltercaps) + g_object_set (infilter, "caps", infiltercaps, NULL); + if (outfiltercaps) + g_object_set (outfilter, "caps", outfiltercaps, NULL); + + fail_unless (gst_element_link_many (src, infilter, deinterlace, outfilter, + sink, NULL)); + if (infiltercaps) + gst_caps_unref (infiltercaps); + if (outfiltercaps) + gst_caps_unref (outfiltercaps); +} + +/* + * Checks if 2 buffers are equal + * + * Equals means same data + */ +static gboolean +test_buffer_equals (GstBuffer * buf_a, GstBuffer * buf_b) +{ + GstMapInfo m1, m2; + gboolean res = FALSE; + + gst_buffer_map (buf_a, &m1, GST_MAP_READ); + gst_buffer_map (buf_b, &m2, GST_MAP_READ); + + if (m1.size == m2.size) { + res = memcmp (m1.data, m2.data, m1.size) == 0; + } + gst_buffer_unmap (buf_a, &m1); + gst_buffer_unmap (buf_b, &m2); + + return res; +} + +static GstPadProbeReturn +sinkpad_enqueue_buffer (GstPad * pad, GstPadProbeInfo * info, gpointer data) +{ + GQueue *queue = (GQueue *) data; + GstBuffer *buf = GST_PAD_PROBE_INFO_BUFFER (info); + + /* enqueue a copy for being compared later */ + g_queue_push_tail (queue, gst_buffer_copy (buf)); + + return GST_PAD_PROBE_OK; +} + +/* + * pad buffer probe that compares the buffer with the top one + * in the GQueue passed as the user data + */ +static GstPadProbeReturn +srcpad_dequeue_and_compare_buffer (GstPad * pad, GstPadProbeInfo * info, + gpointer data) +{ + GQueue *queue = (GQueue *) data; + GstBuffer *buf = GST_PAD_PROBE_INFO_BUFFER (info); + GstBuffer *queue_buf; + + queue_buf = (GstBuffer *) g_queue_pop_head (queue); + fail_if (queue_buf == NULL); + + fail_unless (test_buffer_equals (buf, queue_buf)); + + gst_buffer_unref (queue_buf); + + return GST_PAD_PROBE_OK; +} + +/* + * Utility function that sets up a pipeline with deinterlace for + * validanting that it operates in passthrough mode when receiving + * data with 'infiltercaps' as the input caps and operating in 'mode' mode + */ +static void +deinterlace_check_passthrough (gint mode, const gchar * infiltercaps) +{ + GstMessage *msg; + GQueue *queue; + GstCaps *incaps = NULL; + + if (infiltercaps) + incaps = gst_caps_from_string (infiltercaps); + + setup_test_pipeline (mode, incaps, NULL, 20); + + queue = g_queue_new (); + + /* set up probes for testing */ + gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_BUFFER, sinkpad_enqueue_buffer, + queue, NULL); + gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_BUFFER, + srcpad_dequeue_and_compare_buffer, queue, NULL); + + fail_unless (gst_element_set_state (pipeline, GST_STATE_PLAYING) != + GST_STATE_CHANGE_FAILURE); + + msg = gst_bus_poll (GST_ELEMENT_BUS (pipeline), + GST_MESSAGE_ERROR | GST_MESSAGE_EOS, -1); + if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) { + GST_ERROR ("ERROR: %" GST_PTR_FORMAT, msg); + fail ("Unexpected error message"); + } + gst_message_unref (msg); + + /* queue should be empty */ + fail_unless (g_queue_is_empty (queue)); + + fail_unless (gst_element_set_state (pipeline, GST_STATE_NULL) == + GST_STATE_CHANGE_SUCCESS); + gst_object_unref (pipeline); + gst_object_unref (sinkpad); + gst_object_unref (srcpad); + g_queue_free (queue); +} + +/* + * Sets the caps on deinterlace sinkpad and validates the + * caps that is set on the srcpad + */ +static void +deinterlace_set_caps_and_check (GstCaps * input, gboolean must_deinterlace) +{ + GstCaps *othercaps = NULL; + GstSegment segment; + + gst_pad_send_event (sinkpad, gst_event_new_stream_start ("test")); + fail_unless (gst_pad_set_caps (sinkpad, input)); + gst_segment_init (&segment, GST_FORMAT_TIME); + gst_pad_send_event (sinkpad, gst_event_new_segment (&segment)); + + g_object_get (srcpad, "caps", &othercaps, NULL); + + if (must_deinterlace) { + fail_if (gst_caps_is_interlaced (othercaps)); + } else { + GstStructure *s; + + fail_unless (gst_caps_is_interlaced (input) == + gst_caps_is_interlaced (othercaps)); + + othercaps = gst_caps_make_writable (othercaps); + s = gst_caps_get_structure (othercaps, 0); + gst_structure_remove_field (s, "interlace-mode"); + + input = gst_caps_make_writable (input); + s = gst_caps_get_structure (input, 0); + gst_structure_remove_field (s, "interlace-mode"); + + fail_unless (gst_caps_is_equal (input, othercaps)); + } + gst_caps_unref (input); + gst_caps_unref (othercaps); +} + +static void +deinterlace_set_string_caps_and_check (const gchar * input, + gboolean must_deinterlace) +{ + deinterlace_set_caps_and_check (gst_caps_from_string (input), + must_deinterlace); +} + +GST_START_TEST (test_mode_auto_accept_caps) +{ + setup_deinterlace (); + + /* auto mode */ + g_object_set (deinterlace, "mode", 0, NULL); + fail_unless (gst_element_set_state (deinterlace, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS); + + /* try to set non interlaced caps */ + deinterlace_set_string_caps_and_check (CAPS_YVYU, FALSE); + deinterlace_set_string_caps_and_check (CAPS_YUY2, FALSE); + deinterlace_set_string_caps_and_check (CAPS_YVYU_IMAGE, FALSE); + deinterlace_set_string_caps_and_check (CAPS_YUY2_IMAGE, FALSE); + + /* now try to set interlaced caps */ + deinterlace_set_string_caps_and_check (CAPS_YVYU_INTERLACED, TRUE); + deinterlace_set_string_caps_and_check (CAPS_YUY2_INTERLACED, TRUE); + deinterlace_set_string_caps_and_check (CAPS_YVYU_INTERLACED_IMAGE, TRUE); + deinterlace_set_string_caps_and_check (CAPS_YUY2_INTERLACED_IMAGE, TRUE); + + /* cleanup */ + gst_object_unref (sinkpad); + gst_object_unref (srcpad); + fail_unless (gst_element_set_state (deinterlace, GST_STATE_NULL) == + GST_STATE_CHANGE_SUCCESS); + gst_object_unref (deinterlace); +} + +GST_END_TEST; + +GST_START_TEST (test_mode_forced_accept_caps) +{ + setup_deinterlace (); + + /* forced mode */ + g_object_set (deinterlace, "mode", 1, NULL); + fail_unless (gst_element_set_state (deinterlace, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS); + + /* try to set non interlaced caps */ + deinterlace_set_string_caps_and_check (CAPS_YVYU, TRUE); + deinterlace_set_string_caps_and_check (CAPS_YUY2, TRUE); + deinterlace_set_string_caps_and_check (CAPS_YVYU_IMAGE, TRUE); + deinterlace_set_string_caps_and_check (CAPS_YUY2_IMAGE, TRUE); + + /* now try to set interlaced caps */ + deinterlace_set_string_caps_and_check (CAPS_YVYU_INTERLACED, TRUE); + deinterlace_set_string_caps_and_check (CAPS_YUY2_INTERLACED, TRUE); + deinterlace_set_string_caps_and_check (CAPS_YVYU_INTERLACED_IMAGE, TRUE); + deinterlace_set_string_caps_and_check (CAPS_YUY2_INTERLACED_IMAGE, TRUE); + + /* cleanup */ + gst_object_unref (sinkpad); + gst_object_unref (srcpad); + fail_unless (gst_element_set_state (deinterlace, GST_STATE_NULL) == + GST_STATE_CHANGE_SUCCESS); + gst_object_unref (deinterlace); +} + +GST_END_TEST; + +GST_START_TEST (test_mode_disabled_accept_caps) +{ + setup_deinterlace (); + + /* disabled mode */ + g_object_set (deinterlace, "mode", 2, NULL); + fail_unless (gst_element_set_state (deinterlace, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS); + + /* try to set non interlaced caps */ + deinterlace_set_string_caps_and_check (CAPS_YVYU, FALSE); + deinterlace_set_string_caps_and_check (CAPS_YUY2, FALSE); + deinterlace_set_string_caps_and_check (CAPS_YVYU_IMAGE, FALSE); + deinterlace_set_string_caps_and_check (CAPS_YUY2_IMAGE, FALSE); + + /* now try to set interlaced caps */ + deinterlace_set_string_caps_and_check (CAPS_YVYU_INTERLACED, FALSE); + deinterlace_set_string_caps_and_check (CAPS_YUY2_INTERLACED, FALSE); + deinterlace_set_string_caps_and_check (CAPS_YVYU_INTERLACED_IMAGE, FALSE); + deinterlace_set_string_caps_and_check (CAPS_YUY2_INTERLACED_IMAGE, FALSE); + + /* cleanup */ + gst_object_unref (sinkpad); + gst_object_unref (srcpad); + fail_unless (gst_element_set_state (deinterlace, GST_STATE_NULL) == + GST_STATE_CHANGE_SUCCESS); + gst_object_unref (deinterlace); +} + +GST_END_TEST; + +GST_START_TEST (test_mode_disabled_passthrough) +{ + /* 2 is disabled mode */ + deinterlace_check_passthrough (2, CAPS_YUY2_INTERLACED); + deinterlace_check_passthrough (2, CAPS_YVYU_INTERLACED); + deinterlace_check_passthrough (2, CAPS_YUY2); + deinterlace_check_passthrough (2, CAPS_YVYU); + + deinterlace_check_passthrough (2, CAPS_YUY2_INTERLACED_IMAGE); + deinterlace_check_passthrough (2, CAPS_YVYU_INTERLACED_IMAGE); + deinterlace_check_passthrough (2, CAPS_YUY2_IMAGE); + deinterlace_check_passthrough (2, CAPS_YVYU_IMAGE); +} + +GST_END_TEST; + +GST_START_TEST (test_mode_auto_deinterlaced_passthrough) +{ + /* 0 is auto mode */ + deinterlace_check_passthrough (0, CAPS_YUY2); + deinterlace_check_passthrough (0, CAPS_YVYU); + deinterlace_check_passthrough (0, CAPS_YUY2_IMAGE); + deinterlace_check_passthrough (0, CAPS_YVYU_IMAGE); +} + +GST_END_TEST; + +static Suite * +deinterlace_suite (void) +{ + Suite *s = suite_create ("deinterlace"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_set_timeout (tc_chain, 180); + + if (!gst_registry_check_feature_version (gst_registry_get (), "deinterlace", + GST_VERSION_MAJOR, GST_VERSION_MINOR, GST_VERSION_MICRO)) { + GST_ERROR ("FIXME: port deinterlace element"); + return s; + } + + tcase_add_test (tc_chain, test_create_and_unref); + tcase_add_test (tc_chain, test_mode_auto_accept_caps); + tcase_add_test (tc_chain, test_mode_forced_accept_caps); + tcase_add_test (tc_chain, test_mode_disabled_accept_caps); + tcase_add_test (tc_chain, test_mode_disabled_passthrough); + tcase_add_test (tc_chain, test_mode_auto_deinterlaced_passthrough); + + return s; +} + +GST_CHECK_MAIN (deinterlace); diff --git a/tests/check/elements/deinterleave.c b/tests/check/elements/deinterleave.c new file mode 100644 index 0000000..2ad17b3 --- /dev/null +++ b/tests/check/elements/deinterleave.c @@ -0,0 +1,637 @@ +/* GStreamer unit tests for the interleave element + * Copyright (C) 2008 Sebastian Dröge <slomo@circular-chaos.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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <stdio.h> +#include <gst/audio/audio.h> +#include <gst/check/gstcheck.h> +#include <gst/audio/audio.h> + +GST_START_TEST (test_create_and_unref) +{ + GstElement *deinterleave; + + deinterleave = gst_element_factory_make ("deinterleave", NULL); + fail_unless (deinterleave != NULL); + + gst_element_set_state (deinterleave, GST_STATE_NULL); + gst_object_unref (deinterleave); +} + +GST_END_TEST; + +static GstPad *mysrcpad, **mysinkpads; +static gint nsinkpads; +static GstBus *bus; +static GstElement *deinterleave; + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "format = (string) " GST_AUDIO_NE (F32) ", " + "channels = (int) 1, layout = (string) {interleaved, non-interleaved}, rate = (int) {32000, 48000}")); + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "format = (string) " GST_AUDIO_NE (F32) ", " + "channels = (int) { 2, 3 }, layout = (string) interleaved, rate = (int) {32000, 48000}")); + +#define CAPS_32khz \ + "audio/x-raw, " \ + "format = (string) "GST_AUDIO_NE (F32) ", " \ + "channels = (int) 2, layout = (string) interleaved, " \ + "rate = (int) 32000" + +#define CAPS_48khz \ + "audio/x-raw, " \ + "format = (string) "GST_AUDIO_NE (F32) ", " \ + "channels = (int) 2, layout = (string) interleaved, " \ + "rate = (int) 48000" + +#define CAPS_48khz_3CH \ + "audio/x-raw, " \ + "format = (string) "GST_AUDIO_NE (F32) ", " \ + "channels = (int) 3, layout = (string) interleaved, " \ + "rate = (int) 48000" + +static GstFlowReturn +deinterleave_chain_func (GstPad * pad, GstObject * parent, GstBuffer * buffer) +{ + gint i; + GstMapInfo map; + gfloat *indata; + + fail_unless (GST_IS_BUFFER (buffer)); + gst_buffer_map (buffer, &map, GST_MAP_READ); + indata = (gfloat *) map.data; + fail_unless_equals_int (map.size, 48000 * sizeof (gfloat)); + fail_unless (indata != NULL); + + if (strcmp (GST_PAD_NAME (pad), "sink0") == 0) { + for (i = 0; i < 48000; i++) + fail_unless_equals_float (indata[i], -1.0); + } else if (strcmp (GST_PAD_NAME (pad), "sink1") == 0) { + for (i = 0; i < 48000; i++) + fail_unless_equals_float (indata[i], 1.0); + } else { + g_assert_not_reached (); + } + gst_buffer_unmap (buffer, &map); + gst_buffer_unref (buffer); + + return GST_FLOW_OK; +} + +static void +deinterleave_pad_added (GstElement * src, GstPad * pad, gpointer data) +{ + gchar *name; + gint link = GPOINTER_TO_INT (data); + + if (nsinkpads >= link) + return; + + name = g_strdup_printf ("sink%d", nsinkpads); + + mysinkpads[nsinkpads] = + gst_pad_new_from_static_template (&sinktemplate, name); + g_free (name); + fail_if (mysinkpads[nsinkpads] == NULL); + + gst_pad_set_chain_function (mysinkpads[nsinkpads], deinterleave_chain_func); + fail_unless (gst_pad_link (pad, mysinkpads[nsinkpads]) == GST_PAD_LINK_OK); + gst_pad_set_active (mysinkpads[nsinkpads], TRUE); + nsinkpads++; +} + +GST_START_TEST (test_2_channels) +{ + GstPad *sinkpad; + gint i; + GstBuffer *inbuf; + GstCaps *caps; + gfloat *indata; + GstMapInfo map; + guint64 channel_mask = 0; + + mysinkpads = g_new0 (GstPad *, 2); + nsinkpads = 0; + + deinterleave = gst_element_factory_make ("deinterleave", NULL); + fail_unless (deinterleave != NULL); + + mysrcpad = gst_pad_new_from_static_template (&srctemplate, "src"); + fail_unless (mysrcpad != NULL); + gst_pad_set_active (mysrcpad, TRUE); + + caps = gst_caps_from_string (CAPS_48khz); + channel_mask |= + G_GUINT64_CONSTANT (1) << GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT; + channel_mask |= + G_GUINT64_CONSTANT (1) << GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT; + gst_caps_set_simple (caps, "channel-mask", GST_TYPE_BITMASK, channel_mask, + NULL); + + gst_check_setup_events (mysrcpad, deinterleave, caps, GST_FORMAT_TIME); + + sinkpad = gst_element_get_static_pad (deinterleave, "sink"); + fail_unless (sinkpad != NULL); + fail_unless (gst_pad_link (mysrcpad, sinkpad) == GST_PAD_LINK_OK); + g_object_unref (sinkpad); + + g_signal_connect (deinterleave, "pad-added", + G_CALLBACK (deinterleave_pad_added), GINT_TO_POINTER (2)); + + bus = gst_bus_new (); + gst_element_set_bus (deinterleave, bus); + + fail_unless (gst_element_set_state (deinterleave, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS); + + inbuf = gst_buffer_new_and_alloc (2 * 48000 * sizeof (gfloat)); + inbuf = gst_buffer_make_writable (inbuf); + gst_buffer_map (inbuf, &map, GST_MAP_WRITE); + indata = (gfloat *) map.data; + for (i = 0; i < 2 * 48000; i += 2) { + indata[i] = -1.0; + indata[i + 1] = 1.0; + } + gst_buffer_unmap (inbuf, &map); + + fail_unless (gst_pad_push (mysrcpad, inbuf) == GST_FLOW_OK); + + fail_unless (gst_element_set_state (deinterleave, + GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS); + + for (i = 0; i < nsinkpads; i++) + g_object_unref (mysinkpads[i]); + g_free (mysinkpads); + mysinkpads = NULL; + + g_object_unref (deinterleave); + g_object_unref (bus); + gst_caps_unref (caps); +} + +GST_END_TEST; + +GST_START_TEST (test_2_channels_1_linked) +{ + GstPad *sinkpad; + gint i; + GstBuffer *inbuf; + GstCaps *caps; + gfloat *indata; + GstMapInfo map; + guint64 channel_mask = 0; + + nsinkpads = 0; + mysinkpads = g_new0 (GstPad *, 2); + + deinterleave = gst_element_factory_make ("deinterleave", NULL); + fail_unless (deinterleave != NULL); + + mysrcpad = gst_pad_new_from_static_template (&srctemplate, "src"); + fail_unless (mysrcpad != NULL); + gst_pad_set_active (mysrcpad, TRUE); + + caps = gst_caps_from_string (CAPS_48khz); + channel_mask |= + G_GUINT64_CONSTANT (1) << GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT; + channel_mask |= + G_GUINT64_CONSTANT (1) << GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT; + gst_caps_set_simple (caps, "channel-mask", GST_TYPE_BITMASK, channel_mask, + NULL); + + gst_check_setup_events (mysrcpad, deinterleave, caps, GST_FORMAT_TIME); + + sinkpad = gst_element_get_static_pad (deinterleave, "sink"); + fail_unless (sinkpad != NULL); + fail_unless (gst_pad_link (mysrcpad, sinkpad) == GST_PAD_LINK_OK); + g_object_unref (sinkpad); + + g_signal_connect (deinterleave, "pad-added", + G_CALLBACK (deinterleave_pad_added), GINT_TO_POINTER (1)); + + bus = gst_bus_new (); + gst_element_set_bus (deinterleave, bus); + + fail_unless (gst_element_set_state (deinterleave, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS); + + inbuf = gst_buffer_new_and_alloc (2 * 48000 * sizeof (gfloat)); + inbuf = gst_buffer_make_writable (inbuf); + gst_buffer_map (inbuf, &map, GST_MAP_WRITE); + indata = (gfloat *) map.data; + for (i = 0; i < 2 * 48000; i += 2) { + indata[i] = -1.0; + indata[i + 1] = 1.0; + } + gst_buffer_unmap (inbuf, &map); + + fail_unless (gst_pad_push (mysrcpad, inbuf) == GST_FLOW_OK); + + fail_unless (gst_element_set_state (deinterleave, + GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS); + + for (i = 0; i < nsinkpads; i++) + g_object_unref (mysinkpads[i]); + g_free (mysinkpads); + mysinkpads = NULL; + + g_object_unref (deinterleave); + g_object_unref (bus); + gst_caps_unref (caps); +} + +GST_END_TEST; + +GST_START_TEST (test_2_channels_caps_change) +{ + GstPad *sinkpad; + GstCaps *caps, *caps2; + gint i; + GstBuffer *inbuf; + gfloat *indata; + GstMapInfo map; + guint64 channel_mask; + + nsinkpads = 0; + mysinkpads = g_new0 (GstPad *, 2); + + deinterleave = gst_element_factory_make ("deinterleave", NULL); + fail_unless (deinterleave != NULL); + + mysrcpad = gst_pad_new_from_static_template (&srctemplate, "src"); + fail_unless (mysrcpad != NULL); + + gst_pad_set_active (mysrcpad, TRUE); + + caps = gst_caps_from_string (CAPS_48khz); + channel_mask = 0; + channel_mask |= + G_GUINT64_CONSTANT (1) << GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT; + channel_mask |= + G_GUINT64_CONSTANT (1) << GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT; + gst_caps_set_simple (caps, "channel-mask", GST_TYPE_BITMASK, channel_mask, + NULL); + gst_check_setup_events (mysrcpad, deinterleave, caps, GST_FORMAT_TIME); + + sinkpad = gst_element_get_static_pad (deinterleave, "sink"); + fail_unless (sinkpad != NULL); + fail_unless (gst_pad_link (mysrcpad, sinkpad) == GST_PAD_LINK_OK); + g_object_unref (sinkpad); + + g_signal_connect (deinterleave, "pad-added", + G_CALLBACK (deinterleave_pad_added), GINT_TO_POINTER (2)); + + bus = gst_bus_new (); + gst_element_set_bus (deinterleave, bus); + + fail_unless (gst_element_set_state (deinterleave, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS); + + inbuf = gst_buffer_new_and_alloc (2 * 48000 * sizeof (gfloat)); + inbuf = gst_buffer_make_writable (inbuf); + gst_buffer_map (inbuf, &map, GST_MAP_WRITE); + indata = (gfloat *) map.data; + for (i = 0; i < 2 * 48000; i += 2) { + indata[i] = -1.0; + indata[i + 1] = 1.0; + } + gst_buffer_unmap (inbuf, &map); + + fail_unless (gst_pad_push (mysrcpad, inbuf) == GST_FLOW_OK); + + caps2 = gst_caps_from_string (CAPS_32khz); + channel_mask = 0; + channel_mask |= + G_GUINT64_CONSTANT (1) << GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT; + channel_mask |= + G_GUINT64_CONSTANT (1) << GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT; + gst_caps_set_simple (caps2, "channel-mask", GST_TYPE_BITMASK, channel_mask, + NULL); + gst_pad_set_caps (mysrcpad, caps2); + + inbuf = gst_buffer_new_and_alloc (2 * 48000 * sizeof (gfloat)); + inbuf = gst_buffer_make_writable (inbuf); + gst_buffer_map (inbuf, &map, GST_MAP_WRITE); + indata = (gfloat *) map.data; + for (i = 0; i < 2 * 48000; i += 2) { + indata[i] = -1.0; + indata[i + 1] = 1.0; + } + gst_buffer_unmap (inbuf, &map); + + /* Should work fine because the caps changed in a compatible way */ + fail_unless (gst_pad_push (mysrcpad, inbuf) == GST_FLOW_OK); + + gst_caps_unref (caps2); + + caps2 = gst_caps_from_string (CAPS_48khz_3CH); + channel_mask = 0; + channel_mask |= + G_GUINT64_CONSTANT (1) << GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT; + channel_mask |= + G_GUINT64_CONSTANT (1) << GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT; + channel_mask |= + G_GUINT64_CONSTANT (1) << GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER; + gst_caps_set_simple (caps2, "channel-mask", GST_TYPE_BITMASK, channel_mask, + NULL); + gst_pad_set_caps (mysrcpad, caps2); + + inbuf = gst_buffer_new_and_alloc (3 * 48000 * sizeof (gfloat)); + inbuf = gst_buffer_make_writable (inbuf); + gst_buffer_map (inbuf, &map, GST_MAP_WRITE); + indata = (gfloat *) map.data; + for (i = 0; i < 3 * 48000; i += 3) { + indata[i] = -1.0; + indata[i + 1] = 1.0; + indata[i + 2] = 0.0; + } + gst_buffer_unmap (inbuf, &map); + + /* Should break because the caps changed in an incompatible way */ + fail_if (gst_pad_push (mysrcpad, inbuf) == GST_FLOW_OK); + + fail_unless (gst_element_set_state (deinterleave, + GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS); + + for (i = 0; i < nsinkpads; i++) + g_object_unref (mysinkpads[i]); + g_free (mysinkpads); + mysinkpads = NULL; + + g_object_unref (deinterleave); + g_object_unref (bus); + gst_caps_unref (caps); + gst_caps_unref (caps2); +} + +GST_END_TEST; + + +#define SAMPLES_PER_BUFFER 10 +#define NUM_CHANNELS 8 +#define SAMPLE_RATE 44100 + +static guint pads_created; + +static void +set_channel_positions (GstCaps * caps, int channels, + GstAudioChannelPosition * channelpositions) +{ + int c; + guint64 channel_mask = 0; + + for (c = 0; c < channels; c++) + channel_mask |= G_GUINT64_CONSTANT (1) << channelpositions[c]; + + gst_caps_set_simple (caps, "channel-mask", GST_TYPE_BITMASK, channel_mask, + NULL); +} + +static void +src_handoff_float32_8ch (GstElement * src, GstBuffer * buf, GstPad * pad, + gpointer user_data) +{ + gfloat *data, *p; + guint size, i, c; + + size = sizeof (gfloat) * SAMPLES_PER_BUFFER * NUM_CHANNELS; + data = p = (gfloat *) g_malloc (size); + + for (i = 0; i < SAMPLES_PER_BUFFER; ++i) { + for (c = 0; c < NUM_CHANNELS; ++c) { + *p = (gfloat) ((i * NUM_CHANNELS) + c); + ++p; + } + } + + if (gst_buffer_n_memory (buf)) { + gst_buffer_replace_memory_range (buf, 0, -1, + gst_memory_new_wrapped (0, data, size, 0, size, data, g_free)); + } else { + gst_buffer_insert_memory (buf, 0, + gst_memory_new_wrapped (0, data, size, 0, size, data, g_free)); + } + GST_BUFFER_OFFSET (buf) = 0; + GST_BUFFER_TIMESTAMP (buf) = 0; +} + +static GstPadProbeReturn +src_event_probe (GstPad * pad, GstPadProbeInfo * info, gpointer userdata) +{ + GstAudioChannelPosition layout[NUM_CHANNELS]; + GstCaps *caps; + guint i; + + if ((info->type & GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM) + && GST_EVENT_TYPE (info->data) == GST_EVENT_STREAM_START) { + gst_pad_remove_probe (pad, info->id); + + caps = gst_caps_new_simple ("audio/x-raw", + "format", G_TYPE_STRING, GST_AUDIO_NE (F32), + "channels", G_TYPE_INT, NUM_CHANNELS, + "layout", G_TYPE_STRING, "interleaved", + "rate", G_TYPE_INT, SAMPLE_RATE, NULL); + + for (i = 0; i < NUM_CHANNELS; ++i) + layout[i] = GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT + i; + + set_channel_positions (caps, NUM_CHANNELS, layout); + gst_pad_set_caps (pad, caps); + gst_caps_unref (caps); + } + + return GST_PAD_PROBE_OK; +} + +static GstPadProbeReturn +float_buffer_check_probe (GstPad * pad, GstPadProbeInfo * info, + gpointer userdata) +{ + GstMapInfo map; + gfloat *data; + guint padnum, numpads; + guint num, i; + GstCaps *caps; + GstStructure *s; + GstAudioChannelPosition *pos; + gint channels; + GstBuffer *buffer = GST_PAD_PROBE_INFO_BUFFER (info); + GstAudioInfo audio_info; + guint pad_id = GPOINTER_TO_UINT (userdata); + + fail_unless_equals_int (sscanf (GST_PAD_NAME (pad), "src_%u", &padnum), 1); + + numpads = pads_created; + + /* Check caps */ + caps = gst_pad_get_current_caps (pad); + fail_unless (caps != NULL); + s = gst_caps_get_structure (caps, 0); + fail_unless (gst_structure_get_int (s, "channels", &channels)); + fail_unless_equals_int (channels, 1); + + gst_audio_info_init (&audio_info); + fail_unless (gst_audio_info_from_caps (&audio_info, caps)); + + pos = audio_info.position; + fail_unless (pos != NULL + && pos[0] == GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT + pad_id); + gst_caps_unref (caps); + + gst_buffer_map (buffer, &map, GST_MAP_READ); + data = (gfloat *) map.data; + num = map.size / sizeof (gfloat); + + /* Check buffer content */ + for (i = 0; i < num; ++i) { + guint val, rest; + + val = (guint) data[i]; + GST_LOG ("%s[%u]: %8f", GST_PAD_NAME (pad), i, data[i]); + /* can't use the modulo operator in the assertion statement, since due to + * the way it gets expanded it would be interpreted as a printf operator + * in the failure case, which will result in segfaults */ + rest = val % numpads; + /* check that the first channel is on pad src0, the second on src1 etc. */ + fail_unless_equals_int (rest, padnum); + } + gst_buffer_unmap (buffer, &map); + + return GST_PAD_PROBE_OK; /* don't drop data */ +} + +static void +pad_added_setup_data_check_float32_8ch_cb (GstElement * deinterleave, + GstPad * pad, GstElement * pipeline) +{ + GstElement *queue, *sink; + GstPad *sinkpad; + + queue = gst_element_factory_make ("queue", NULL); + fail_unless (queue != NULL); + + sink = gst_element_factory_make ("fakesink", NULL); + fail_unless (sink != NULL); + + gst_bin_add_many (GST_BIN (pipeline), queue, sink, NULL); + gst_element_link_pads_full (queue, "src", sink, "sink", + GST_PAD_LINK_CHECK_NOTHING); + + sinkpad = gst_element_get_static_pad (queue, "sink"); + + fail_unless_equals_int (gst_pad_link (pad, sinkpad), GST_PAD_LINK_OK); + gst_object_unref (sinkpad); + + gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER, float_buffer_check_probe, + GUINT_TO_POINTER (pads_created), NULL); + + gst_element_set_state (sink, GST_STATE_PLAYING); + gst_element_set_state (queue, GST_STATE_PLAYING); + + GST_LOG ("new pad: %s", GST_PAD_NAME (pad)); + ++pads_created; +} + +static GstElement * +make_fake_src_8chans_float32 (void) +{ + GstElement *src; + GstPad *pad; + + src = gst_element_factory_make ("fakesrc", "src"); + fail_unless (src != NULL, "failed to create fakesrc element"); + + g_object_set (src, "num-buffers", 1, NULL); + g_object_set (src, "signal-handoffs", TRUE, NULL); + + g_signal_connect (src, "handoff", G_CALLBACK (src_handoff_float32_8ch), NULL); + + pad = gst_element_get_static_pad (src, "src"); + gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, src_event_probe, + NULL, NULL); + gst_object_unref (pad); + + return src; +} + +GST_START_TEST (test_8_channels_float32) +{ + GstElement *pipeline, *src, *deinterleave; + GstMessage *msg; + + pipeline = (GstElement *) gst_pipeline_new ("pipeline"); + fail_unless (pipeline != NULL, "failed to create pipeline"); + + src = make_fake_src_8chans_float32 (); + + deinterleave = gst_element_factory_make ("deinterleave", "deinterleave"); + fail_unless (deinterleave != NULL, "failed to create deinterleave element"); + g_object_set (deinterleave, "keep-positions", TRUE, NULL); + + gst_bin_add_many (GST_BIN (pipeline), src, deinterleave, NULL); + + fail_unless (gst_element_link (src, deinterleave), + "failed to link src <=> deinterleave"); + + g_signal_connect (deinterleave, "pad-added", + G_CALLBACK (pad_added_setup_data_check_float32_8ch_cb), pipeline); + + pads_created = 0; + + gst_element_set_state (pipeline, GST_STATE_PLAYING); + + msg = gst_bus_poll (GST_ELEMENT_BUS (pipeline), GST_MESSAGE_EOS, -1); + gst_message_unref (msg); + + fail_unless_equals_int (pads_created, NUM_CHANNELS); + + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_object_unref (pipeline); +} + +GST_END_TEST; + +static Suite * +deinterleave_suite (void) +{ + Suite *s = suite_create ("deinterleave"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_set_timeout (tc_chain, 180); + tcase_add_test (tc_chain, test_create_and_unref); + tcase_add_test (tc_chain, test_2_channels); + tcase_add_test (tc_chain, test_2_channels_1_linked); + tcase_add_test (tc_chain, test_2_channels_caps_change); + tcase_add_test (tc_chain, test_8_channels_float32); + + return s; +} + +GST_CHECK_MAIN (deinterleave); diff --git a/tests/check/elements/dtmf.c b/tests/check/elements/dtmf.c new file mode 100755 index 0000000..c2ae36d --- /dev/null +++ b/tests/check/elements/dtmf.c @@ -0,0 +1,588 @@ +/* GStreamer + * + * unit test for dtmf elements + * Copyright (C) 2013 Collabora Ltd + * @author: Olivier Crete <olivier.crete@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 + +#include <gst/gst.h> +#include <gst/audio/audio.h> +#include <gst/check/gstcheck.h> +#include <gst/check/gsttestclock.h> +#include <gst/rtp/gstrtpbuffer.h> + + +/* Include this from the plugin to get the defines */ + +#include "../../gst/dtmf/gstdtmfcommon.h" + +#define END_BIT (1<<7) + +static GstStaticPadTemplate audio_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "format = (string) \"" GST_AUDIO_NE (S16) "\", " + "rate = " GST_AUDIO_RATE_RANGE ", " "channels = (int) 1") + ); + +static GstStaticPadTemplate rtp_dtmf_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-rtp, " + "media = (string) \"audio\", " + "payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", " + "clock-rate = (int) [ 0, MAX ], " + "encoding-name = (string) \"TELEPHONE-EVENT\"") + ); + + +static void +check_get_dtmf_event_message (GstBus * bus, gint number, gint volume) +{ + GstMessage *message; + gboolean have_message = FALSE; + + while (!have_message && + (message = gst_bus_pop_filtered (bus, GST_MESSAGE_ELEMENT)) != NULL) { + if (gst_message_has_name (message, "dtmf-event")) { + const GstStructure *s = gst_message_get_structure (message); + gint stype, snumber, smethod, svolume; + + fail_unless (gst_structure_get (s, + "type", G_TYPE_INT, &stype, + "number", G_TYPE_INT, &snumber, + "method", G_TYPE_INT, &smethod, + "volume", G_TYPE_INT, &svolume, NULL)); + + fail_unless (stype == 1); + fail_unless (smethod == 1); + fail_unless (snumber == number); + fail_unless (svolume == volume); + have_message = TRUE; + } + gst_message_unref (message); + } + + fail_unless (have_message); +} + +static void +check_no_dtmf_event_message (GstBus * bus) +{ + GstMessage *message; + gboolean have_message = FALSE; + + while (!have_message && + (message = gst_bus_pop_filtered (bus, GST_MESSAGE_ELEMENT)) != NULL) { + if (gst_message_has_name (message, "dtmf-event") || + gst_message_has_name (message, "dtmf-event-processed") || + gst_message_has_name (message, "dtmf-event-dropped")) { + have_message = TRUE; + } + gst_message_unref (message); + } + + fail_unless (!have_message); +} + +static void +check_buffers_duration (GstClockTime expected_duration) +{ + GstClockTime duration = 0; + + while (buffers) { + GstBuffer *buf = buffers->data; + + buffers = g_list_delete_link (buffers, buffers); + + fail_unless (GST_BUFFER_DURATION_IS_VALID (buf)); + duration += GST_BUFFER_DURATION (buf); + gst_buffer_unref (buf); + } + + fail_unless (duration == expected_duration); +} + +static void +send_rtp_packet (GstPad * src, guint timestamp, gboolean marker, gboolean end, + guint number, guint volume, guint duration) +{ + GstBuffer *buf; + GstRTPBuffer rtpbuf = GST_RTP_BUFFER_INIT; + gchar *payload; + static guint seqnum = 1; + + buf = gst_rtp_buffer_new_allocate (4, 0, 0); + fail_unless (gst_rtp_buffer_map (buf, GST_MAP_READWRITE, &rtpbuf)); + gst_rtp_buffer_set_seq (&rtpbuf, seqnum++); + gst_rtp_buffer_set_timestamp (&rtpbuf, timestamp); + gst_rtp_buffer_set_marker (&rtpbuf, marker); + payload = gst_rtp_buffer_get_payload (&rtpbuf); + payload[0] = number; + payload[1] = volume | (end ? END_BIT : 0); + GST_WRITE_UINT16_BE (payload + 2, duration); + gst_rtp_buffer_unmap (&rtpbuf); + fail_unless (gst_pad_push (src, buf) == GST_FLOW_OK); +} + +GST_START_TEST (test_rtpdtmfdepay) +{ + GstElement *dtmfdepay; + GstPad *src, *sink; + GstBus *bus; + GstCaps *caps_in; + GstCaps *expected_caps_out; + GstCaps *caps_out; + + dtmfdepay = gst_check_setup_element ("rtpdtmfdepay"); + sink = gst_check_setup_sink_pad (dtmfdepay, &audio_sink_template); + src = gst_check_setup_src_pad (dtmfdepay, &rtp_dtmf_src_template); + + bus = gst_bus_new (); + gst_element_set_bus (dtmfdepay, bus); + + gst_pad_set_active (src, TRUE); + gst_pad_set_active (sink, TRUE); + gst_element_set_state (dtmfdepay, GST_STATE_PLAYING); + + + caps_in = gst_caps_new_simple ("application/x-rtp", + "encoding-name", G_TYPE_STRING, "TELEPHONE-EVENT", + "media", G_TYPE_STRING, "audio", + "clock-rate", G_TYPE_INT, 1000, "payload", G_TYPE_INT, 99, NULL); + gst_check_setup_events (src, dtmfdepay, caps_in, GST_FORMAT_TIME); + gst_caps_unref (caps_in); + + caps_out = gst_pad_get_current_caps (sink); + expected_caps_out = gst_caps_new_simple ("audio/x-raw", + "format", G_TYPE_STRING, GST_AUDIO_NE (S16), + "rate", G_TYPE_INT, 1000, "channels", G_TYPE_INT, 1, NULL); + fail_unless (gst_caps_is_equal_fixed (caps_out, expected_caps_out)); + gst_caps_unref (expected_caps_out); + gst_caps_unref (caps_out); + + /* Single packet DTMF */ + send_rtp_packet (src, 200, TRUE, TRUE, 1, 5, 250); + check_get_dtmf_event_message (bus, 1, 5); + check_buffers_duration (250 * GST_MSECOND); + + /* Two packet DTMF */ + send_rtp_packet (src, 800, TRUE, FALSE, 1, 5, 200); + send_rtp_packet (src, 800, FALSE, TRUE, 1, 5, 400); + check_buffers_duration (400 * GST_MSECOND); + check_get_dtmf_event_message (bus, 1, 5); + + /* Long DTMF */ + send_rtp_packet (src, 3000, TRUE, FALSE, 1, 5, 200); + check_get_dtmf_event_message (bus, 1, 5); + check_buffers_duration (200 * GST_MSECOND); + send_rtp_packet (src, 3000, FALSE, FALSE, 1, 5, 400); + check_no_dtmf_event_message (bus); + check_buffers_duration (200 * GST_MSECOND); + send_rtp_packet (src, 3000, FALSE, FALSE, 1, 5, 600); + check_no_dtmf_event_message (bus); + check_buffers_duration (200 * GST_MSECOND); + + /* New without end to last */ + send_rtp_packet (src, 4000, TRUE, TRUE, 1, 5, 250); + check_get_dtmf_event_message (bus, 1, 5); + check_buffers_duration (250 * GST_MSECOND); + + check_no_dtmf_event_message (bus); + fail_unless (buffers == NULL); + gst_element_set_bus (dtmfdepay, NULL); + gst_object_unref (bus); + + gst_pad_set_active (src, FALSE); + gst_pad_set_active (sink, FALSE); + gst_check_teardown_sink_pad (dtmfdepay); + gst_check_teardown_src_pad (dtmfdepay); + gst_check_teardown_element (dtmfdepay); +} + +GST_END_TEST; + + +static GstStaticPadTemplate rtp_dtmf_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-rtp, " + "media = (string) \"audio\", " + "payload = (int) 99, " + "clock-rate = (int) 1000, " + "seqnum-offset = (uint) 333, " + "timestamp-offset = (uint) 666, " + "ssrc = (uint) 999, " + "maxptime = (uint) 20, encoding-name = (string) \"TELEPHONE-EVENT\"") + ); + +GstElement *dtmfsrc; +GstPad *sink; +GstClock *testclock; +GstBus *bus; + +static void +check_message_structure (GstStructure * expected_s) +{ + GstMessage *message; + gboolean have_message = FALSE; + + while (!have_message && + (message = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, + GST_MESSAGE_ELEMENT)) != NULL) { + if (gst_message_has_name (message, gst_structure_get_name (expected_s))) { + const GstStructure *s = gst_message_get_structure (message); + + fail_unless (gst_structure_is_equal (s, expected_s)); + have_message = TRUE; + } + gst_message_unref (message); + } + + fail_unless (have_message); + + gst_structure_free (expected_s); +} + +static void +check_rtp_buffer (GstClockTime ts, GstClockTime duration, gboolean start, + gboolean end, guint rtpts, guint ssrc, guint volume, guint number, + guint rtpduration) +{ + GstBuffer *buffer; + GstRTPBuffer rtpbuffer = GST_RTP_BUFFER_INIT; + gchar *payload; + + g_mutex_lock (&check_mutex); + while (buffers == NULL) + g_cond_wait (&check_cond, &check_mutex); + g_mutex_unlock (&check_mutex); + fail_unless (buffers != NULL); + + buffer = buffers->data; + buffers = g_list_delete_link (buffers, buffers); + + fail_unless (GST_BUFFER_PTS (buffer) == ts); + fail_unless (GST_BUFFER_DURATION (buffer) == duration); + + fail_unless (gst_rtp_buffer_map (buffer, GST_MAP_READ, &rtpbuffer)); + fail_unless (gst_rtp_buffer_get_marker (&rtpbuffer) == start); + fail_unless (gst_rtp_buffer_get_timestamp (&rtpbuffer) == rtpts); + payload = gst_rtp_buffer_get_payload (&rtpbuffer); + + fail_unless (payload[0] == number); + fail_unless ((payload[1] & 0x7F) == volume); + fail_unless (! !(payload[1] & 0x80) == end); + fail_unless (GST_READ_UINT16_BE (payload + 2) == rtpduration); + + gst_rtp_buffer_unmap (&rtpbuffer); + gst_buffer_unref (buffer); +} + +gint method; + +static void +setup_rtpdtmfsrc (void) +{ + testclock = gst_test_clock_new (); + bus = gst_bus_new (); + + method = 1; + dtmfsrc = gst_check_setup_element ("rtpdtmfsrc"); + sink = gst_check_setup_sink_pad (dtmfsrc, &rtp_dtmf_sink_template); + gst_element_set_bus (dtmfsrc, bus); + fail_unless (gst_element_set_clock (dtmfsrc, testclock)); + + gst_pad_set_active (sink, TRUE); + fail_unless (gst_element_set_state (dtmfsrc, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS); +} + +static void +teardown_dtmfsrc (void) +{ + gst_object_unref (testclock); + gst_pad_set_active (sink, FALSE); + gst_element_set_bus (dtmfsrc, NULL); + gst_object_unref (bus); + gst_check_teardown_sink_pad (dtmfsrc); + gst_check_teardown_element (dtmfsrc); +} + +GST_START_TEST (test_dtmfsrc_invalid_events) +{ + GstStructure *s; + + /* Missing start */ + s = gst_structure_new ("dtmf-event", + "type", G_TYPE_INT, 1, "number", G_TYPE_INT, 3, + "method", G_TYPE_INT, method, "volume", G_TYPE_INT, 8, NULL); + fail_unless (gst_pad_push_event (sink, + gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, s)) == FALSE); + + /* Missing volume */ + s = gst_structure_new ("dtmf-event", + "type", G_TYPE_INT, 1, "number", G_TYPE_INT, 3, + "method", G_TYPE_INT, method, "start", G_TYPE_BOOLEAN, TRUE, NULL); + fail_unless (gst_pad_push_event (sink, + gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, s)) == FALSE); + + /* Missing number */ + s = gst_structure_new ("dtmf-event", + "type", G_TYPE_INT, 1, "method", G_TYPE_INT, method, + "volume", G_TYPE_INT, 8, "start", G_TYPE_BOOLEAN, TRUE, NULL); + fail_unless (gst_pad_push_event (sink, + gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, s)) == FALSE); + + /* Missing type */ + s = gst_structure_new ("dtmf-event", + "number", G_TYPE_INT, 3, "method", G_TYPE_INT, method, + "volume", G_TYPE_INT, 8, "start", G_TYPE_BOOLEAN, TRUE, NULL); + fail_unless (gst_pad_push_event (sink, + gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, s)) == FALSE); + + /* Stop before start */ + s = gst_structure_new ("dtmf-event", + "type", G_TYPE_INT, 1, "number", G_TYPE_INT, 3, + "method", G_TYPE_INT, method, "volume", G_TYPE_INT, 8, + "start", G_TYPE_BOOLEAN, FALSE, NULL); + fail_unless (gst_pad_push_event (sink, + gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, s)) == FALSE); + + gst_element_set_state (dtmfsrc, GST_STATE_NULL); +} + +GST_END_TEST; + +GST_START_TEST (test_rtpdtmfsrc_min_duration) +{ + GstStructure *s; + GstClockID id; + guint timestamp = 0; + GstCaps *expected_caps, *caps; + + /* Minimum duration dtmf */ + + s = gst_structure_new ("dtmf-event", + "type", G_TYPE_INT, 1, "number", G_TYPE_INT, 3, + "method", G_TYPE_INT, 1, "volume", G_TYPE_INT, 8, + "start", G_TYPE_BOOLEAN, TRUE, NULL); + fail_unless (gst_pad_push_event (sink, + gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, + gst_structure_copy (s)))); + check_no_dtmf_event_message (bus); + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (testclock), NULL); + fail_unless (buffers == NULL); + id = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (testclock)); + fail_unless (gst_clock_id_get_time (id) == 0); + gst_clock_id_unref (id); + gst_structure_set_name (s, "dtmf-event-processed"); + check_message_structure (s); + + s = gst_structure_new ("dtmf-event", + "type", G_TYPE_INT, 1, "method", G_TYPE_INT, 1, + "start", G_TYPE_BOOLEAN, FALSE, NULL); + fail_unless (gst_pad_push_event (sink, + gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, + gst_structure_copy (s)))); + + check_rtp_buffer (0, 20 * GST_MSECOND, TRUE, FALSE, 666, 999, 8, 3, 20); + + for (timestamp = 20; timestamp < MIN_PULSE_DURATION + 20; timestamp += 20) { + gst_test_clock_advance_time (GST_TEST_CLOCK (testclock), + 20 * GST_MSECOND + 1); + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (testclock), NULL); + fail_unless (buffers == NULL); + id = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (testclock)); + fail_unless (gst_clock_id_get_time (id) == timestamp * GST_MSECOND); + gst_clock_id_unref (id); + + if (timestamp < MIN_PULSE_DURATION) { + check_rtp_buffer (timestamp * GST_MSECOND, 20 * GST_MSECOND, FALSE, + FALSE, 666, 999, 8, 3, timestamp + 20); + check_no_dtmf_event_message (bus); + } else { + gst_structure_set_name (s, "dtmf-event-processed"); + check_message_structure (s); + check_rtp_buffer (timestamp * GST_MSECOND, + (20 + MIN_INTER_DIGIT_INTERVAL) * GST_MSECOND, FALSE, TRUE, 666, + 999, 8, 3, timestamp + 20); + } + + fail_unless (buffers == NULL); + } + + + fail_unless (gst_test_clock_peek_id_count (GST_TEST_CLOCK (testclock)) == 0); + + /* caps check */ + + expected_caps = gst_caps_new_simple ("application/x-rtp", + "encoding-name", G_TYPE_STRING, "TELEPHONE-EVENT", + "media", G_TYPE_STRING, "audio", + "clock-rate", G_TYPE_INT, 1000, "payload", G_TYPE_INT, 99, + "seqnum-offset", G_TYPE_UINT, 333, + "timestamp-offset", G_TYPE_UINT, 666, + "ssrc", G_TYPE_UINT, 999, "ptime", G_TYPE_UINT, 20, NULL); + caps = gst_pad_get_current_caps (sink); + fail_unless (gst_caps_can_intersect (caps, expected_caps)); + gst_caps_unref (caps); + gst_caps_unref (expected_caps); + + gst_element_set_state (dtmfsrc, GST_STATE_NULL); + + check_no_dtmf_event_message (bus); +} + +GST_END_TEST; + +static GstStaticPadTemplate audio_dtmfsrc_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "format = (string) \"" GST_AUDIO_NE (S16) "\", " + "rate = (int) 8003, " "channels = (int) 1") + ); +static void +setup_dtmfsrc (void) +{ + testclock = gst_test_clock_new (); + bus = gst_bus_new (); + + method = 2; + dtmfsrc = gst_check_setup_element ("dtmfsrc"); + sink = gst_check_setup_sink_pad (dtmfsrc, &audio_dtmfsrc_sink_template); + gst_element_set_bus (dtmfsrc, bus); + fail_unless (gst_element_set_clock (dtmfsrc, testclock)); + + gst_pad_set_active (sink, TRUE); + fail_unless (gst_element_set_state (dtmfsrc, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS); +} + + +GST_START_TEST (test_dtmfsrc_min_duration) +{ + GstStructure *s; + GstClockID id; + GstClockTime timestamp = 0; + GstCaps *expected_caps, *caps; + guint interval; + + g_object_get (dtmfsrc, "interval", &interval, NULL); + fail_unless (interval == 50); + + /* Minimum duration dtmf */ + gst_test_clock_set_time (GST_TEST_CLOCK (testclock), 0); + + s = gst_structure_new ("dtmf-event", + "type", G_TYPE_INT, 1, "number", G_TYPE_INT, 3, + "method", G_TYPE_INT, 2, "volume", G_TYPE_INT, 8, + "start", G_TYPE_BOOLEAN, TRUE, NULL); + fail_unless (gst_pad_push_event (sink, + gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, + gst_structure_copy (s)))); + + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (testclock), NULL); + id = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (testclock)); + fail_unless (gst_clock_id_get_time (id) == 0); + gst_clock_id_unref (id); + + gst_structure_set_name (s, "dtmf-event-processed"); + check_message_structure (s); + + s = gst_structure_new ("dtmf-event", + "type", G_TYPE_INT, 1, "method", G_TYPE_INT, 2, + "start", G_TYPE_BOOLEAN, FALSE, NULL); + fail_unless (gst_pad_push_event (sink, + gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, + gst_structure_copy (s)))); + + for (timestamp = interval * GST_MSECOND; + timestamp < (MIN_PULSE_DURATION + MIN_INTER_DIGIT_INTERVAL) * + GST_MSECOND; timestamp += GST_MSECOND * interval) { + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (testclock), NULL); + gst_test_clock_advance_time (GST_TEST_CLOCK (testclock), + interval * GST_MSECOND); + + id = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (testclock)); + fail_unless (gst_clock_id_get_time (id) == timestamp); + gst_clock_id_unref (id); + } + + gst_structure_set_name (s, "dtmf-event-processed"); + check_message_structure (s); + + check_buffers_duration ((MIN_PULSE_DURATION + MIN_INTER_DIGIT_INTERVAL) * + GST_MSECOND); + + fail_unless (gst_test_clock_peek_id_count (GST_TEST_CLOCK (testclock)) == 0); + + /* caps check */ + + expected_caps = gst_caps_new_simple ("audio/x-raw", + "format", G_TYPE_STRING, GST_AUDIO_NE (S16), + "rate", G_TYPE_INT, 8003, "channels", G_TYPE_INT, 1, NULL); + caps = gst_pad_get_current_caps (sink); + fail_unless (gst_caps_can_intersect (caps, expected_caps)); + gst_caps_unref (caps); + gst_caps_unref (expected_caps); + + gst_element_set_state (dtmfsrc, GST_STATE_NULL); + + check_no_dtmf_event_message (bus); +} + +GST_END_TEST; + +static Suite * +dtmf_suite (void) +{ + Suite *s = suite_create ("dtmf"); + TCase *tc; + + tc = tcase_create ("rtpdtmfdepay"); + tcase_add_test (tc, test_rtpdtmfdepay); + suite_add_tcase (s, tc); + + tc = tcase_create ("rtpdtmfsrc"); + tcase_add_checked_fixture (tc, setup_rtpdtmfsrc, teardown_dtmfsrc); + tcase_add_test (tc, test_dtmfsrc_invalid_events); + tcase_add_test (tc, test_rtpdtmfsrc_min_duration); + suite_add_tcase (s, tc); + + tc = tcase_create ("dtmfsrc"); + tcase_add_checked_fixture (tc, setup_dtmfsrc, teardown_dtmfsrc); + tcase_add_test (tc, test_dtmfsrc_invalid_events); + tcase_add_test (tc, test_dtmfsrc_min_duration); + suite_add_tcase (s, tc); + + return s; +} + + +GST_CHECK_MAIN (dtmf); diff --git a/tests/check/elements/equalizer.c b/tests/check/elements/equalizer.c new file mode 100644 index 0000000..491e039 --- /dev/null +++ b/tests/check/elements/equalizer.c @@ -0,0 +1,395 @@ +/* GStreamer + * + * Copyright (C) 2008 Sebastian Dröge <slomo@circular-chaos.org> + * + * equalizer.c: Unit test for the equalizer element + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <gst/gst.h> +#include <gst/audio/audio.h> +#include <gst/base/gstbasetransform.h> +#include <gst/check/gstcheck.h> + +#include <math.h> + +/* 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 */ +GstPad *mysrcpad, *mysinkpad; + +#define EQUALIZER_CAPS_STRING \ + "audio/x-raw, " \ + "format = (string) "GST_AUDIO_NE (F64) ", " \ + "layout = (string) interleaved, " \ + "channels = (int) 1, " \ + "rate = (int) 48000" + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "format = (string) " GST_AUDIO_NE (F64) ", " + "layout = (string) interleaved, " + "channels = (int) 1, " "rate = (int) 48000") + ); +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "format = (string) " GST_AUDIO_NE (F64) ", " + "layout = (string) interleaved, " + "channels = (int) 1, " "rate = (int) 48000") + ); + +static GstElement * +setup_equalizer (void) +{ + GstElement *equalizer; + + GST_DEBUG ("setup_equalizer"); + equalizer = gst_check_setup_element ("equalizer-nbands"); + mysrcpad = gst_check_setup_src_pad (equalizer, &srctemplate); + mysinkpad = gst_check_setup_sink_pad (equalizer, &sinktemplate); + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + + return equalizer; +} + +static void +cleanup_equalizer (GstElement * equalizer) +{ + GST_DEBUG ("cleanup_equalizer"); + + g_list_foreach (buffers, (GFunc) gst_mini_object_unref, NULL); + g_list_free (buffers); + buffers = NULL; + + gst_pad_set_active (mysrcpad, FALSE); + gst_pad_set_active (mysinkpad, FALSE); + gst_check_teardown_src_pad (equalizer); + gst_check_teardown_sink_pad (equalizer); + gst_check_teardown_element (equalizer); +} + +GST_START_TEST (test_equalizer_5bands_passthrough) +{ + GstElement *equalizer; + GstBuffer *inbuffer; + GstCaps *caps; + gdouble *in, *res; + gint i; + GstMapInfo map; + + equalizer = setup_equalizer (); + g_object_set (G_OBJECT (equalizer), "num-bands", 5, NULL); + + fail_unless_equals_int (gst_child_proxy_get_children_count (GST_CHILD_PROXY + (equalizer)), 5); + + fail_unless (gst_element_set_state (equalizer, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (1024 * sizeof (gdouble)); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 1024; i++) + in[i] = g_random_double_range (-1.0, 1.0); + gst_buffer_unmap (inbuffer, &map); + + caps = gst_caps_from_string (EQUALIZER_CAPS_STRING); + gst_check_setup_events (mysrcpad, equalizer, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ())); + /* ... and puts a new buffer on the global list */ + fail_unless (g_list_length (buffers) == 1); + + gst_buffer_map (GST_BUFFER (buffers->data), &map, GST_MAP_READ); + res = (gdouble *) map.data; + + for (i = 0; i < 1024; i++) + fail_unless_equals_float (in[i], res[i]); + gst_buffer_unmap (GST_BUFFER (buffers->data), &map); + + /* cleanup */ + cleanup_equalizer (equalizer); +} + +GST_END_TEST; + +GST_START_TEST (test_equalizer_5bands_minus_24) +{ + GstElement *equalizer; + GstBuffer *inbuffer; + GstCaps *caps; + gdouble *in, *res, rms_in, rms_out; + gint i; + GstMapInfo map; + + equalizer = setup_equalizer (); + g_object_set (G_OBJECT (equalizer), "num-bands", 5, NULL); + + fail_unless_equals_int (gst_child_proxy_get_children_count (GST_CHILD_PROXY + (equalizer)), 5); + + for (i = 0; i < 5; i++) { + GObject *band = + gst_child_proxy_get_child_by_index (GST_CHILD_PROXY (equalizer), i); + fail_unless (band != NULL); + + g_object_set (band, "gain", -24.0, NULL); + g_object_unref (band); + } + + fail_unless (gst_element_set_state (equalizer, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (1024 * sizeof (gdouble)); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 1024; i++) + in[i] = g_random_double_range (-1.0, 1.0); + gst_buffer_unmap (inbuffer, &map); + + rms_in = 0.0; + for (i = 0; i < 1024; i++) + rms_in += in[i] * in[i]; + rms_in = sqrt (rms_in / 1024); + + caps = gst_caps_from_string (EQUALIZER_CAPS_STRING); + gst_check_setup_events (mysrcpad, equalizer, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ())); + /* ... and puts a new buffer on the global list */ + fail_unless (g_list_length (buffers) == 1); + + gst_buffer_map (GST_BUFFER (buffers->data), &map, GST_MAP_READ); + res = (gdouble *) map.data; + + rms_out = 0.0; + for (i = 0; i < 1024; i++) + rms_out += res[i] * res[i]; + rms_out = sqrt (rms_out / 1024); + gst_buffer_unmap (GST_BUFFER (buffers->data), &map); + + fail_unless (rms_in > rms_out); + + /* cleanup */ + cleanup_equalizer (equalizer); +} + +GST_END_TEST; + +GST_START_TEST (test_equalizer_5bands_plus_12) +{ + GstElement *equalizer; + GstBuffer *inbuffer; + GstCaps *caps; + gdouble *in, *res, rms_in, rms_out; + gint i; + GstMapInfo map; + + equalizer = setup_equalizer (); + g_object_set (G_OBJECT (equalizer), "num-bands", 5, NULL); + + fail_unless_equals_int (gst_child_proxy_get_children_count (GST_CHILD_PROXY + (equalizer)), 5); + + for (i = 0; i < 5; i++) { + GObject *band = + gst_child_proxy_get_child_by_index (GST_CHILD_PROXY (equalizer), i); + fail_unless (band != NULL); + + g_object_set (band, "gain", 12.0, NULL); + g_object_unref (band); + } + + fail_unless (gst_element_set_state (equalizer, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (1024 * sizeof (gdouble)); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + in = (gdouble *) map.data; + for (i = 0; i < 1024; i++) + in[i] = g_random_double_range (-1.0, 1.0); + gst_buffer_unmap (inbuffer, &map); + + rms_in = 0.0; + for (i = 0; i < 1024; i++) + rms_in += in[i] * in[i]; + rms_in = sqrt (rms_in / 1024); + + caps = gst_caps_from_string (EQUALIZER_CAPS_STRING); + gst_check_setup_events (mysrcpad, equalizer, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ())); + /* ... and puts a new buffer on the global list */ + fail_unless (g_list_length (buffers) == 1); + + gst_buffer_map (GST_BUFFER (buffers->data), &map, GST_MAP_READ); + res = (gdouble *) map.data; + + rms_out = 0.0; + for (i = 0; i < 1024; i++) + rms_out += res[i] * res[i]; + rms_out = sqrt (rms_out / 1024); + gst_buffer_unmap (GST_BUFFER (buffers->data), &map); + + fail_unless (rms_in < rms_out); + + /* cleanup */ + cleanup_equalizer (equalizer); +} + +GST_END_TEST; + +GST_START_TEST (test_equalizer_band_number_changing) +{ + GstElement *equalizer; + gint i; + + equalizer = setup_equalizer (); + + g_object_set (G_OBJECT (equalizer), "num-bands", 5, NULL); + fail_unless_equals_int (gst_child_proxy_get_children_count (GST_CHILD_PROXY + (equalizer)), 5); + + for (i = 0; i < 5; i++) { + GObject *band; + + band = gst_child_proxy_get_child_by_index (GST_CHILD_PROXY (equalizer), i); + fail_unless (band != NULL); + g_object_unref (band); + } + + g_object_set (G_OBJECT (equalizer), "num-bands", 10, NULL); + fail_unless_equals_int (gst_child_proxy_get_children_count (GST_CHILD_PROXY + (equalizer)), 10); + + for (i = 0; i < 10; i++) { + GObject *band; + + band = gst_child_proxy_get_child_by_index (GST_CHILD_PROXY (equalizer), i); + fail_unless (band != NULL); + g_object_unref (band); + } + + /* cleanup */ + cleanup_equalizer (equalizer); +} + +GST_END_TEST; + +GST_START_TEST (test_equalizer_presets) +{ + GstElement *eq1, *eq2; + gint type; + gdouble gain, freq; + + eq1 = gst_check_setup_element ("equalizer-nbands"); + g_object_set (G_OBJECT (eq1), "num-bands", 3, NULL); + + /* set properties to non-defaults */ + gst_child_proxy_set ((GstChildProxy *) eq1, + "band0::type", 0, "band0::gain", -3.0, "band0::freq", 100.0, + "band1::type", 1, "band1::gain", +3.0, "band1::freq", 1000.0, + "band2::type", 2, "band2::gain", +9.0, "band2::freq", 10000.0, NULL); + + /* save preset */ + gst_preset_save_preset ((GstPreset *) eq1, "_testpreset_"); + GST_INFO_OBJECT (eq1, "Preset saved"); + + eq2 = gst_check_setup_element ("equalizer-nbands"); + g_object_set (G_OBJECT (eq2), "num-bands", 3, NULL); + + /* load preset */ + gst_preset_load_preset ((GstPreset *) eq2, "_testpreset_"); + GST_INFO_OBJECT (eq1, "Preset loaded"); + + /* compare properties */ + gst_child_proxy_get ((GstChildProxy *) eq2, + "band0::type", &type, "band0::gain", &gain, "band0::freq", &freq, NULL); + ck_assert_int_eq (type, 0); + fail_unless (gain == -3.0, NULL); + fail_unless (freq == 100.0, NULL); + gst_child_proxy_get ((GstChildProxy *) eq2, + "band1::type", &type, "band1::gain", &gain, "band1::freq", &freq, NULL); + ck_assert_int_eq (type, 1); + fail_unless (gain == +3.0, NULL); + fail_unless (freq == 1000.0, NULL); + gst_child_proxy_get ((GstChildProxy *) eq2, + "band2::type", &type, "band2::gain", &gain, "band2::freq", &freq, NULL); + ck_assert_int_eq (type, 2); + fail_unless (gain == +9.0, NULL); + fail_unless (freq == 10000.0, NULL); + + gst_preset_delete_preset ((GstPreset *) eq1, "_testpreset_"); + gst_check_teardown_element (eq1); + gst_check_teardown_element (eq2); +} + +GST_END_TEST; + + +static Suite * +equalizer_suite (void) +{ + Suite *s = suite_create ("equalizer"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_equalizer_5bands_passthrough); + tcase_add_test (tc_chain, test_equalizer_5bands_minus_24); + tcase_add_test (tc_chain, test_equalizer_5bands_plus_12); + tcase_add_test (tc_chain, test_equalizer_band_number_changing); + tcase_add_test (tc_chain, test_equalizer_presets); + + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = equalizer_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/flacparse.c b/tests/check/elements/flacparse.c new file mode 100644 index 0000000..306f010 --- /dev/null +++ b/tests/check/elements/flacparse.c @@ -0,0 +1,300 @@ +/* + * GStreamer + * + * unit test for flacparse + * + * Copyright (C) 2010 Nokia Corporation. All rights reserved. + * + * Contact: Stefan Kost <stefan.kost@nokia.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. + */ + +#include <gst/check/gstcheck.h> +#include "parser.h" + +#define SRC_CAPS_TMPL "audio/x-flac, framed=(boolean)false" +#define SINK_CAPS_TMPL "audio/x-flac, framed=(boolean)true" + +GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (SINK_CAPS_TMPL) + ); + +GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (SRC_CAPS_TMPL) + ); + +/* some data */ +static guint8 streaminfo_header[] = { + 0x7f, 0x46, 0x4c, 0x41, 0x43, 0x01, 0x00, 0x00, + 0x02, 0x66, 0x4c, 0x61, 0x43, 0x00, 0x00, 0x00, + 0x22, 0x12, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0a, 0xc4, 0x40, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00 +}; + +static guint8 comment_header[] = { + 0x84, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 +}; + +static guint8 flac_frame[] = { + 0xff, 0xf8, 0xa9, 0x08, 0x00, 0x50, 0x18, 0x06, + 0x6a, 0x0c, 0xce, 0x13, 0x24, 0x19, 0x68, 0x00, + 0x46, 0x23, 0x08, 0xca, 0xcb, 0x58, 0x9c, 0x26, + 0x92, 0x30, 0xa6, 0x29, 0x8a, 0xca, 0xd1, 0x18, + 0xae, 0x26, 0x5c, 0x90, 0x60, 0xbf, 0x11, 0xad, + 0x43, 0x02, 0x06, 0x26, 0xbd, 0x35, 0xdd, 0xa3, + 0x11, 0xa6, 0x4d, 0x18, 0x8c, 0x9a, 0xe4, 0x62, + 0xd9, 0x23, 0x11, 0x8b, 0xcb, 0x56, 0x55, 0x45, + 0xc2, 0x18, 0x56, 0xa2, 0xe2, 0xe1, 0x18, 0x99, + 0x54, 0x98, 0x46, 0x4d, 0x08, 0x70, 0x9a, 0x64, + 0xc4, 0x18, 0x4f, 0x27, 0x64, 0x31, 0x66, 0x27, + 0x79, 0x19, 0x3c, 0x8c, 0x8c, 0xa3, 0x44, 0x18, + 0x23, 0xd2, 0x6b, 0x8b, 0x64, 0x8c, 0x21, 0x84, + 0xd6, 0x23, 0x13, 0x13, 0x2d, 0x44, 0xca, 0x5a, + 0x23, 0x09, 0x93, 0x25, 0x18, 0x10, 0x61, 0x38, + 0xb4, 0x60, 0x8f, 0x2c, 0x8d, 0x26, 0xb4, 0xc9, + 0xd9, 0x19, 0x19, 0x34, 0xd7, 0x31, 0x06, 0x10, + 0xc4, 0x30, 0x83, 0x17, 0xe2, 0x0c, 0x2c, 0xc4, + 0xc8, 0xc9, 0x3c, 0x5e, 0x93, 0x11, 0x8a, 0x62, + 0x64, 0x8c, 0x26, 0x23, 0x22, 0x30, 0x9a, 0x58, + 0x86, 0x04, 0x18, 0x4c, 0xab, 0x2b, 0x26, 0x5c, + 0x46, 0x88, 0xcb, 0xb1, 0x0d, 0x26, 0xbb, 0x5e, + 0x8c, 0xa7, 0x64, 0x31, 0x3d, 0x31, 0x06, 0x26, + 0x43, 0x17, 0xa3, 0x08, 0x61, 0x06, 0x17, 0xc4, + 0x62, 0xec, 0x4d, 0x4b, 0x2e, 0x2d, 0x4a, 0x94, + 0xa4, 0xc2, 0x31, 0x4c, 0x4c, 0x20, 0xc0, 0x83, + 0x14, 0x8c, 0x27, 0x8b, 0x31, 0x23, 0x2f, 0x23, + 0x11, 0x91, 0x94, 0x65, 0x1a, 0x20, 0xc2, 0x18, + 0x86, 0x51, 0x88, 0x62, 0x7c, 0x43, 0x2e, 0xa3, + 0x04, 0x18, 0x8c, 0x20, 0xc2, 0xf5, 0xaa, 0x94, + 0xc2, 0x31, 0x32, 0xd2, 0xb2, 0xa2, 0x30, 0xba, + 0x10, 0xc2, 0xb5, 0x89, 0xa5, 0x18, 0x10, 0x62, + 0x9a, 0x10, 0x61, 0x19, 0x72, 0x71, 0x1a, 0xb9, + 0x0c, 0x23, 0x46, 0x10, 0x62, 0x78, 0x81, 0x82, + 0x3d, 0x75, 0xea, 0x6b, 0x51, 0x8b, 0x61, 0x06, + 0x08, 0x62, 0x32, 0x5e, 0x84, 0x18, 0x27, 0x25, + 0xc2, 0x6a, 0x4b, 0x51, 0x31, 0x34, 0x5e, 0x29, + 0xa1, 0x3c, 0x4d, 0x26, 0x23, 0x10, 0xc2, 0x6b, + 0xb1, 0x0d, 0x11, 0xae, 0x46, 0x88, 0x31, 0x35, + 0xb1, 0x06, 0x08, 0x79, 0x7e, 0x4f, 0x53, 0x23, + 0x29, 0xa4, 0x30, 0x20, 0x30, 0x23, 0x5a, 0xb2, + 0xc8, 0x60, 0x9c, 0x93, 0x13, 0x17, 0x92, 0x98, + 0x46, 0x13, 0x54, 0x53, 0x08, 0xcb, 0x13, 0xa1, + 0x1a, 0x89, 0xe5, 0x46, 0x08, 0x18, 0x10, 0x30, + 0x9d, 0x68, 0xc2, 0x1c, 0x46, 0x46, 0xae, 0x62, + 0x1a, 0x46, 0x4e, 0x4d, 0x34, 0x8c, 0xbd, 0x26, + 0xc0, 0x40, 0x62, 0xc9, 0xa9, 0x31, 0x74, 0xa8, + 0x99, 0x52, 0xb0, 0x8c, 0xa9, 0x29, 0x84, 0x61, + 0x19, 0x54, 0x43, 0x02, 0x06, 0x04, 0x32, 0xe5, + 0x18, 0x21, 0x91, 0x8b, 0xf2, 0xcc, 0x10, 0x30, + 0x8e, 0x23, 0xc4, 0x76, 0x43, 0x08, 0x30, 0x83, + 0x08, 0x62, 0x6c, 0x4e, 0xe2, 0x35, 0x96, 0xd0, + 0x8e, 0x89, 0x97, 0x42, 0x18, 0x91, 0x84, 0x61, + 0x3c, 0x26, 0xa5, 0x2c, 0x4e, 0x17, 0x94, 0xb8, + 0xb5, 0xa4, 0xcb, 0x88, 0xc9, 0x84, 0x18, 0xb9, + 0x84, 0x19, 0x23, 0x2d, 0xa4, 0x64, 0x62, 0x18, + 0x86, 0x53, 0x93, 0xcb, 0x30, 0x8f, 0x2f, 0x93, + 0x55, 0xc4, 0xd7, 0x08, 0x62, 0xb8, 0x46, 0x84, + 0x68, 0xa3, 0x02, 0xaf, 0x33 +}; + +static guint8 garbage_frame[] = { + 0xff, 0xff, 0xff, 0xff, 0xff +}; + + +GST_START_TEST (test_parse_flac_normal) +{ + gst_parser_test_normal (flac_frame, sizeof (flac_frame)); +} + +GST_END_TEST; + + +GST_START_TEST (test_parse_flac_drain_single) +{ + gst_parser_test_drain_single (flac_frame, sizeof (flac_frame)); +} + +GST_END_TEST; + + +GST_START_TEST (test_parse_flac_drain_garbage) +{ + /* We always output the after frame garbage too because we + * have no way of detecting it + */ +#if 0 + gst_parser_test_drain_garbage (flac_frame, sizeof (flac_frame), + garbage_frame, sizeof (garbage_frame)); +#endif + guint8 frame[sizeof (flac_frame) + sizeof (garbage_frame)]; + + memcpy (frame, flac_frame, sizeof (flac_frame)); + memcpy (frame + sizeof (flac_frame), garbage_frame, sizeof (garbage_frame)); + + gst_parser_test_drain_single (frame, sizeof (frame)); +} + +GST_END_TEST; + + +GST_START_TEST (test_parse_flac_split) +{ + gst_parser_test_split (flac_frame, sizeof (flac_frame)); +} + +GST_END_TEST; + + +GST_START_TEST (test_parse_flac_skip_garbage) +{ + /* We always include the garbage into the frame because + * we have no easy way for finding the real end of the + * frame. The decoder will later skip the garbage + */ +#if 0 + gst_parser_test_skip_garbage (flac_frame, sizeof (flac_frame), + garbage_frame, sizeof (garbage_frame)); +#endif + guint8 frame[sizeof (flac_frame) + sizeof (garbage_frame)]; + + memcpy (frame, flac_frame, sizeof (flac_frame)); + memcpy (frame + sizeof (flac_frame), garbage_frame, sizeof (garbage_frame)); + + gst_parser_test_normal (frame, sizeof (frame)); +} + +GST_END_TEST; + + +#define structure_get_int(s,f) \ + (g_value_get_int(gst_structure_get_value(s,f))) +#define fail_unless_structure_field_int_equals(s,field,num) \ + fail_unless_equals_int (structure_get_int(s,field), num) +/* + * Test if the parser handles raw stream and codec_data info properly. + */ +GST_START_TEST (test_parse_flac_detect_stream) +{ + GstCaps *caps; + GstStructure *s; + const GValue *streamheader; + GArray *bufarr; + gint i; + + /* Push random data. It should get through since the parser should be + * initialized because it got codec_data in the caps */ + caps = gst_parser_test_get_output_caps (flac_frame, sizeof (flac_frame), + SRC_CAPS_TMPL); + fail_unless (caps != NULL); + + /* Check that the negotiated caps are as expected */ + /* When codec_data is present, parser assumes that data is version 4 */ + GST_LOG ("flac output caps: %" GST_PTR_FORMAT, caps); + s = gst_caps_get_structure (caps, 0); + fail_unless (gst_structure_has_name (s, "audio/x-flac")); + fail_unless_structure_field_int_equals (s, "channels", 1); + fail_unless_structure_field_int_equals (s, "rate", 44100); + fail_unless (gst_structure_has_field (s, "streamheader")); + streamheader = gst_structure_get_value (s, "streamheader"); + fail_unless (G_VALUE_TYPE (streamheader) == GST_TYPE_ARRAY); + bufarr = g_value_peek_pointer (streamheader); + fail_unless (bufarr->len == 2); + for (i = 0; i < bufarr->len; i++) { + GstBuffer *buf; + GValue *bufval = &g_array_index (bufarr, GValue, i); + + fail_unless (G_VALUE_TYPE (bufval) == GST_TYPE_BUFFER); + buf = g_value_peek_pointer (bufval); + if (i == 0) { + fail_unless (gst_buffer_get_size (buf) == sizeof (streaminfo_header)); + fail_unless (gst_buffer_memcmp (buf, 0, streaminfo_header, + sizeof (streaminfo_header)) == 0); + } else if (i == 1) { + fail_unless (gst_buffer_get_size (buf) == sizeof (comment_header)); + fail_unless (gst_buffer_memcmp (buf, 0, comment_header, + sizeof (comment_header)) == 0); + } + } + + gst_caps_unref (caps); +} + +GST_END_TEST; + +static Suite * +flacparse_suite (void) +{ + Suite *s = suite_create ("flacparse"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_parse_flac_normal); + tcase_add_test (tc_chain, test_parse_flac_drain_single); + tcase_add_test (tc_chain, test_parse_flac_drain_garbage); + tcase_add_test (tc_chain, test_parse_flac_split); + tcase_add_test (tc_chain, test_parse_flac_skip_garbage); + + /* Other tests */ + tcase_add_test (tc_chain, test_parse_flac_detect_stream); + + return s; +} + + +/* + * TODO: + * - Both push- and pull-modes need to be tested + * * Pull-mode & EOS + */ + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = flacparse_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + /* init test context */ + ctx_factory = "flacparse"; + ctx_sink_template = &sinktemplate; + ctx_src_template = &srctemplate; + ctx_discard = 3; + ctx_headers[0].data = streaminfo_header; + ctx_headers[0].size = sizeof (streaminfo_header); + ctx_headers[1].data = comment_header; + ctx_headers[1].size = sizeof (comment_header); + /* custom offsets, and ts always repeatedly 0 */ + ctx_no_metadata = TRUE; + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/flvdemux.c b/tests/check/elements/flvdemux.c new file mode 100644 index 0000000..c9d156d --- /dev/null +++ b/tests/check/elements/flvdemux.c @@ -0,0 +1,182 @@ +/* GStreamer unit tests for flvdemux + * + * Copyright (C) 2009 Tim-Philipp Müller <tim centricular net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <gst/check/gstcheck.h> + +#include <gst/gst.h> + +static void +pad_added_cb (GstElement * flvdemux, GstPad * pad, GstBin * pipeline) +{ + GstElement *sink; + + sink = gst_bin_get_by_name (pipeline, "fakesink"); + fail_unless (gst_element_link (flvdemux, sink)); + gst_object_unref (sink); + + gst_element_set_state (sink, GST_STATE_PAUSED); +} + +static GstBusSyncReply +error_cb (GstBus * bus, GstMessage * msg, gpointer user_data) +{ + if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) { + const gchar *file = (const gchar *) user_data; + GError *err = NULL; + gchar *dbg = NULL; + + gst_message_parse_error (msg, &err, &dbg); + g_error ("ERROR for %s: %s\n%s\n", file, err->message, dbg); + } + + return GST_BUS_PASS; +} + +static void +handoff_cb (GstElement * element, GstBuffer * buf, GstPad * pad, + gint * p_counter) +{ + *p_counter += 1; + GST_LOG ("counter = %d", *p_counter); +} + +static void +process_file (const gchar * file, gboolean push_mode, gint repeat, + gint num_buffers) +{ + GstElement *src, *sep, *sink, *flvdemux, *pipeline; + GstBus *bus; + gchar *path; + gint counter; + + pipeline = gst_pipeline_new ("pipeline"); + fail_unless (pipeline != NULL, "Failed to create pipeline!"); + + bus = gst_element_get_bus (pipeline); + + /* kids, don't use a sync handler for this at home, really; we do because + * we just want to abort and nothing else */ + gst_bus_set_sync_handler (bus, error_cb, (gpointer) file, NULL); + + src = gst_element_factory_make ("filesrc", "filesrc"); + fail_unless (src != NULL, "Failed to create 'filesrc' element!"); + + if (push_mode) { + sep = gst_element_factory_make ("queue", "queue"); + fail_unless (sep != NULL, "Failed to create 'queue' element"); + } else { + sep = gst_element_factory_make ("identity", "identity"); + fail_unless (sep != NULL, "Failed to create 'identity' element"); + } + + flvdemux = gst_element_factory_make ("flvdemux", "flvdemux"); + fail_unless (flvdemux != NULL, "Failed to create 'flvdemux' element!"); + + sink = gst_element_factory_make ("fakesink", "fakesink"); + fail_unless (sink != NULL, "Failed to create 'fakesink' element!"); + + g_object_set (sink, "signal-handoffs", TRUE, NULL); + g_signal_connect (sink, "handoff", G_CALLBACK (handoff_cb), &counter); + + gst_bin_add_many (GST_BIN (pipeline), src, sep, flvdemux, sink, NULL); + + fail_unless (gst_element_link (src, sep)); + fail_unless (gst_element_link (sep, flvdemux)); + + /* can't link flvdemux and sink yet, do that later */ + g_signal_connect (flvdemux, "pad-added", G_CALLBACK (pad_added_cb), pipeline); + + path = g_build_filename (GST_TEST_FILES_PATH, file, NULL); + GST_LOG ("processing file '%s'", path); + g_object_set (src, "location", path, NULL); + + do { + GstStateChangeReturn state_ret; + GstMessage *msg; + + GST_LOG ("repeat=%d", repeat); + + counter = 0; + + state_ret = gst_element_set_state (pipeline, GST_STATE_PAUSED); + fail_unless (state_ret != GST_STATE_CHANGE_FAILURE); + + if (state_ret == GST_STATE_CHANGE_ASYNC) { + GST_LOG ("waiting for pipeline to reach PAUSED state"); + state_ret = gst_element_get_state (pipeline, NULL, NULL, -1); + fail_unless_equals_int (state_ret, GST_STATE_CHANGE_SUCCESS); + } + + GST_LOG ("PAUSED, let's read all of it"); + + state_ret = gst_element_set_state (pipeline, GST_STATE_PLAYING); + fail_unless (state_ret != GST_STATE_CHANGE_FAILURE); + + msg = gst_bus_poll (bus, GST_MESSAGE_EOS, -1); + fail_unless (msg != NULL, "Expected EOS message on bus! (%s)", file); + + gst_message_unref (msg); + + if (num_buffers >= 0) { + fail_unless_equals_int (counter, num_buffers); + } + + fail_unless_equals_int (gst_element_set_state (pipeline, GST_STATE_NULL), + GST_STATE_CHANGE_SUCCESS); + + --repeat; + } while (repeat > 0); + + gst_object_unref (bus); + gst_object_unref (pipeline); + + g_free (path); +} + +GST_START_TEST (test_reuse_pull) +{ + process_file ("pcm16sine.flv", FALSE, 3, 129); + gst_task_cleanup_all (); +} + +GST_END_TEST; + +GST_START_TEST (test_reuse_push) +{ + process_file ("pcm16sine.flv", TRUE, 3, 129); + gst_task_cleanup_all (); +} + +GST_END_TEST; + +static Suite * +flvdemux_suite (void) +{ + Suite *s = suite_create ("flvdemux"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_reuse_push); + tcase_add_test (tc_chain, test_reuse_pull); + + return s; +} + +GST_CHECK_MAIN (flvdemux) diff --git a/tests/check/elements/flvmux.c b/tests/check/elements/flvmux.c new file mode 100644 index 0000000..1df2efe --- /dev/null +++ b/tests/check/elements/flvmux.c @@ -0,0 +1,175 @@ +/* GStreamer unit tests for flvmux + * + * Copyright (C) 2009 Tim-Philipp Müller <tim centricular net> + * + * 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 + +#ifdef HAVE_VALGRIND +# include <valgrind/valgrind.h> +#endif + +#include <gst/check/gstcheck.h> + +#include <gst/gst.h> + +static GstBusSyncReply +error_cb (GstBus * bus, GstMessage * msg, gpointer user_data) +{ + if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) { + GError *err = NULL; + gchar *dbg = NULL; + + gst_message_parse_error (msg, &err, &dbg); + g_error ("ERROR: %s\n%s\n", err->message, dbg); + } + + return GST_BUS_PASS; +} + +static void +handoff_cb (GstElement * element, GstBuffer * buf, GstPad * pad, + gint * p_counter) +{ + *p_counter += 1; + GST_LOG ("counter = %d", *p_counter); +} + +static void +mux_pcm_audio (guint num_buffers, guint repeat) +{ + GstElement *src, *sink, *flvmux, *conv, *pipeline; + GstPad *sinkpad, *srcpad; + gint counter; + + GST_LOG ("num_buffers = %u", num_buffers); + + pipeline = gst_pipeline_new ("pipeline"); + fail_unless (pipeline != NULL, "Failed to create pipeline!"); + + /* kids, don't use a sync handler for this at home, really; we do because + * we just want to abort and nothing else */ + gst_bus_set_sync_handler (GST_ELEMENT_BUS (pipeline), error_cb, NULL, NULL); + + src = gst_element_factory_make ("audiotestsrc", "audiotestsrc"); + fail_unless (src != NULL, "Failed to create 'audiotestsrc' element!"); + + g_object_set (src, "num-buffers", num_buffers, NULL); + + conv = gst_element_factory_make ("audioconvert", "audioconvert"); + fail_unless (conv != NULL, "Failed to create 'audioconvert' element!"); + + flvmux = gst_element_factory_make ("flvmux", "flvmux"); + fail_unless (flvmux != NULL, "Failed to create 'flvmux' element!"); + + sink = gst_element_factory_make ("fakesink", "fakesink"); + fail_unless (sink != NULL, "Failed to create 'fakesink' element!"); + + g_object_set (sink, "signal-handoffs", TRUE, NULL); + g_signal_connect (sink, "handoff", G_CALLBACK (handoff_cb), &counter); + + gst_bin_add_many (GST_BIN (pipeline), src, conv, flvmux, sink, NULL); + + fail_unless (gst_element_link (src, conv)); + fail_unless (gst_element_link (flvmux, sink)); + + /* now link the elements */ + sinkpad = gst_element_get_request_pad (flvmux, "audio"); + fail_unless (sinkpad != NULL, "Could not get audio request pad"); + + srcpad = gst_element_get_static_pad (conv, "src"); + fail_unless (srcpad != NULL, "Could not get audioconvert's source pad"); + + fail_unless_equals_int (gst_pad_link (srcpad, sinkpad), GST_PAD_LINK_OK); + + gst_object_unref (srcpad); + gst_object_unref (sinkpad); + + do { + GstStateChangeReturn state_ret; + GstMessage *msg; + + GST_LOG ("repeat=%d", repeat); + + counter = 0; + + state_ret = gst_element_set_state (pipeline, GST_STATE_PAUSED); + fail_unless (state_ret != GST_STATE_CHANGE_FAILURE); + + if (state_ret == GST_STATE_CHANGE_ASYNC) { + GST_LOG ("waiting for pipeline to reach PAUSED state"); + state_ret = gst_element_get_state (pipeline, NULL, NULL, -1); + fail_unless_equals_int (state_ret, GST_STATE_CHANGE_SUCCESS); + } + + GST_LOG ("PAUSED, let's do the rest of it"); + + state_ret = gst_element_set_state (pipeline, GST_STATE_PLAYING); + fail_unless (state_ret != GST_STATE_CHANGE_FAILURE); + + msg = gst_bus_poll (GST_ELEMENT_BUS (pipeline), GST_MESSAGE_EOS, -1); + fail_unless (msg != NULL, "Expected EOS message on bus!"); + + GST_LOG ("EOS"); + gst_message_unref (msg); + + /* should have some output */ + fail_unless (counter > 2); + + fail_unless_equals_int (gst_element_set_state (pipeline, GST_STATE_NULL), + GST_STATE_CHANGE_SUCCESS); + + /* repeat = test re-usability */ + --repeat; + } while (repeat > 0); + + gst_object_unref (pipeline); +} + +GST_START_TEST (test_index_writing) +{ + /* note: there's a magic 128 value in flvmux when doing index writing */ + if ((__i__ % 33) == 1) + mux_pcm_audio (__i__, 2); +} + +GST_END_TEST; + +static Suite * +flvmux_suite (void) +{ + Suite *s = suite_create ("flvmux"); + TCase *tc_chain = tcase_create ("general"); + gint loop = 499; + + suite_add_tcase (s, tc_chain); + +#ifdef HAVE_VALGRIND + if (RUNNING_ON_VALGRIND) { + loop = 140; + } +#endif + + tcase_add_loop_test (tc_chain, test_index_writing, 1, loop); + + return s; +} + +GST_CHECK_MAIN (flvmux) diff --git a/tests/check/elements/gdkpixbufsink.c b/tests/check/elements/gdkpixbufsink.c new file mode 100644 index 0000000..1183ccb --- /dev/null +++ b/tests/check/elements/gdkpixbufsink.c @@ -0,0 +1,293 @@ +/* GStreamer unit test for the gdkpixbufsink element + * Copyright (C) 2008 Tim-Philipp Müller <tim centricular net> + * + * 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 + +#include <gst/check/gstcheck.h> +#include <gst/video/video.h> + +#include <gdk-pixbuf/gdk-pixbuf.h> + +#define CAPS_RGB "video/x-raw, format=RGB" +#define CAPS_RGBA "video/x-raw, format=RGBA" +#define WxH ",width=(int)319,height=(int)241" + +#define N_BUFFERS 5 + +typedef struct +{ + GstElement *pipe; + GstElement *src; + GstElement *filter; + GstElement *sink; +} GstGdkPixbufSinkTestContext; + +static void +gdkpixbufsink_init_test_context (GstGdkPixbufSinkTestContext * ctx, + const gchar * filter_caps_string, gint num_buffers) +{ + GstCaps *caps; + + fail_unless (ctx != NULL); + + ctx->pipe = gst_pipeline_new ("pipeline"); + fail_unless (ctx->pipe != NULL); + ctx->src = gst_element_factory_make ("videotestsrc", "src"); + fail_unless (ctx->src != NULL, "Failed to create videotestsrc element"); + ctx->filter = gst_element_factory_make ("capsfilter", "filter"); + fail_unless (ctx->filter != NULL, "Failed to create capsfilter element"); + ctx->sink = gst_element_factory_make ("gdkpixbufsink", "sink"); + fail_unless (ctx->sink != NULL, "Failed to create gdkpixbufsink element"); + + caps = gst_caps_from_string (filter_caps_string); + fail_unless (caps != NULL); + g_object_set (ctx->filter, "caps", caps, NULL); + gst_caps_unref (caps); + + if (num_buffers > 0) + g_object_set (ctx->src, "num-buffers", num_buffers, NULL); + + fail_unless (gst_bin_add (GST_BIN (ctx->pipe), ctx->src)); + fail_unless (gst_bin_add (GST_BIN (ctx->pipe), ctx->filter)); + fail_unless (gst_bin_add (GST_BIN (ctx->pipe), ctx->sink)); + fail_unless (gst_element_link (ctx->src, ctx->filter)); + fail_unless (gst_element_link (ctx->filter, ctx->sink)); +} + +static void +gdkpixbufsink_unset_test_context (GstGdkPixbufSinkTestContext * ctx) +{ + gst_element_set_state (ctx->pipe, GST_STATE_NULL); + gst_object_unref (ctx->pipe); + memset (ctx, 0, sizeof (GstGdkPixbufSinkTestContext)); +} + +static gboolean +check_last_pixbuf (GstGdkPixbufSinkTestContext * ctx, gpointer pixbuf) +{ + gpointer last_pb = NULL; + gboolean ret; + + g_object_get (ctx->sink, "last-pixbuf", &last_pb, NULL); + + ret = (last_pb == pixbuf); + + if (last_pb) + g_object_unref (last_pb); + + return ret; +} + +/* doesn't return a ref to the pixbuf */ +static GdkPixbuf * +check_message_pixbuf (GstMessage * msg, const gchar * name, gint channels, + gboolean has_alpha) +{ + GdkPixbuf *pixbuf; + const GstStructure *s; + + fail_unless (gst_message_get_structure (msg) != NULL); + + s = gst_message_get_structure (msg); + fail_unless_equals_string (gst_structure_get_name (s), name); + + fail_unless (gst_structure_has_field (s, "pixbuf")); + fail_unless (gst_structure_has_field_typed (s, "pixel-aspect-ratio", + GST_TYPE_FRACTION)); + pixbuf = + GDK_PIXBUF (g_value_get_object (gst_structure_get_value (s, "pixbuf"))); + fail_unless (GDK_IS_PIXBUF (pixbuf)); + fail_unless_equals_int (gdk_pixbuf_get_n_channels (pixbuf), channels); + fail_unless_equals_int (gdk_pixbuf_get_has_alpha (pixbuf), has_alpha); + fail_unless_equals_int (gdk_pixbuf_get_width (pixbuf), 319); + fail_unless_equals_int (gdk_pixbuf_get_height (pixbuf), 241); + + return pixbuf; +} + +GST_START_TEST (test_rgb) +{ + GstGdkPixbufSinkTestContext ctx; + GstMessage *msg; + GdkPixbuf *pixbuf; + GstBus *bus; + gint i; + + gdkpixbufsink_init_test_context (&ctx, CAPS_RGB WxH, N_BUFFERS); + + fail_unless (check_last_pixbuf (&ctx, NULL)); + + /* start prerolling */ + fail_unless_equals_int (gst_element_set_state (ctx.pipe, GST_STATE_PAUSED), + GST_STATE_CHANGE_ASYNC); + + /* wait until prerolled */ + fail_unless_equals_int (gst_element_get_state (ctx.pipe, NULL, NULL, -1), + GST_STATE_CHANGE_SUCCESS); + + bus = gst_element_get_bus (ctx.pipe); + + /* find element message from our gdkpixbufsink, ignore element messages from + * other elemements (which just seems prudent to do, we don't expect any) */ + while (1) { + msg = gst_bus_pop_filtered (bus, GST_MESSAGE_ELEMENT); + fail_if (msg == NULL, "Expected element message from gdkpixbufsink"); + + if (msg->src == GST_OBJECT (ctx.sink)) + break; + } + + pixbuf = check_message_pixbuf (msg, "preroll-pixbuf", 3, FALSE); + fail_unless (check_last_pixbuf (&ctx, pixbuf)); + gst_message_unref (msg); + pixbuf = NULL; + + /* and go! */ + fail_unless_equals_int (gst_element_set_state (ctx.pipe, GST_STATE_PLAYING), + GST_STATE_CHANGE_SUCCESS); + + /* This is racy, supposed to make sure locking and refcounting works at + * least to some extent */ + for (i = 0; i < 10000; ++i) { + gpointer obj = NULL; + + g_object_get (ctx.sink, "last-pixbuf", &obj, NULL); + fail_unless (obj == NULL || GDK_IS_PIXBUF (obj)); + if (obj) + g_object_unref (obj); + } + + /* there should be as many pixbuf messages as buffers */ + for (i = 0; i < N_BUFFERS; ++i) { + /* find element message from our gdkpixbufsink, ignore element messages from + * other elemements (which just seems prudent to do, we don't expect any) */ + while (1) { + msg = gst_bus_timed_pop_filtered (bus, -1, GST_MESSAGE_ELEMENT); + fail_if (msg == NULL, "Expected element message from gdkpixbufsink"); + if (msg->src == GST_OBJECT (ctx.sink)) + break; + } + + pixbuf = check_message_pixbuf (msg, "pixbuf", 3, FALSE); + gst_message_unref (msg); + } + + /* note: we don't hold a ref to pixbuf any longer here, but it should be ok */ + fail_unless (check_last_pixbuf (&ctx, pixbuf)); + pixbuf = NULL; + + gdkpixbufsink_unset_test_context (&ctx); + gst_object_unref (bus); +} + +GST_END_TEST; + +GST_START_TEST (test_rgba) +{ + GstGdkPixbufSinkTestContext ctx; + GstMessage *msg; + GdkPixbuf *pixbuf; + GstBus *bus; + gint i; + + gdkpixbufsink_init_test_context (&ctx, CAPS_RGBA WxH, N_BUFFERS); + + fail_unless (check_last_pixbuf (&ctx, NULL)); + + /* start prerolling */ + fail_unless_equals_int (gst_element_set_state (ctx.pipe, GST_STATE_PAUSED), + GST_STATE_CHANGE_ASYNC); + + /* wait until prerolled */ + fail_unless_equals_int (gst_element_get_state (ctx.pipe, NULL, NULL, -1), + GST_STATE_CHANGE_SUCCESS); + + bus = gst_element_get_bus (ctx.pipe); + + /* find element message from our gdkpixbufsink, ignore element messages from + * other elemements (which just seems prudent to do, we don't expect any) */ + while (1) { + msg = gst_bus_pop_filtered (bus, GST_MESSAGE_ELEMENT); + fail_if (msg == NULL, "Expected element message from gdkpixbufsink"); + + if (msg->src == GST_OBJECT (ctx.sink)) + break; + } + + pixbuf = check_message_pixbuf (msg, "preroll-pixbuf", 4, TRUE); + fail_unless (check_last_pixbuf (&ctx, pixbuf)); + gst_message_unref (msg); + pixbuf = NULL; + + /* and go! */ + fail_unless_equals_int (gst_element_set_state (ctx.pipe, GST_STATE_PLAYING), + GST_STATE_CHANGE_SUCCESS); + + /* This is racy, supposed to make sure locking and refcounting works at + * least to some extent */ + for (i = 0; i < 10000; ++i) { + gpointer obj = NULL; + + g_object_get (ctx.sink, "last-pixbuf", &obj, NULL); + fail_unless (obj == NULL || GDK_IS_PIXBUF (obj)); + if (obj) + g_object_unref (obj); + } + + /* there should be as many pixbuf messages as buffers */ + for (i = 0; i < N_BUFFERS; ++i) { + /* find element message from our gdkpixbufsink, ignore element messages from + * other elemements (which just seems prudent to do, we don't expect any) */ + while (1) { + msg = gst_bus_timed_pop_filtered (bus, -1, GST_MESSAGE_ELEMENT); + fail_if (msg == NULL, "Expected element message from gdkpixbufsink"); + if (msg->src == GST_OBJECT (ctx.sink)) + break; + } + + pixbuf = check_message_pixbuf (msg, "pixbuf", 4, TRUE); + gst_message_unref (msg); + } + + /* note: we don't hold a ref to pixbuf any longer here, but it should be ok */ + fail_unless (check_last_pixbuf (&ctx, pixbuf)); + pixbuf = NULL; + + gdkpixbufsink_unset_test_context (&ctx); + gst_object_unref (bus); +} + +GST_END_TEST; + +static Suite * +gdkpixbufsink_suite (void) +{ + Suite *s = suite_create ("gdkpixbufsink"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_rgb); + tcase_add_test (tc_chain, test_rgba); + + return s; +} + +GST_CHECK_MAIN (gdkpixbufsink); diff --git a/tests/check/elements/icydemux.c b/tests/check/elements/icydemux.c new file mode 100644 index 0000000..525e309 --- /dev/null +++ b/tests/check/elements/icydemux.c @@ -0,0 +1,300 @@ +/* + * icydemux.c - Test icydemux element + * Copyright (C) 2006 Michael Smith <msmith@fluendo.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 + +#include <gst/check/gstcheck.h> + +/* Chunk of data: 8 bytes, followed by a metadata-length byte of 2, followed by + * some metadata (32 bytes), then some more data. + */ +#define TEST_METADATA \ + "Test metadata" +#define ICY_METADATA \ + "StreamTitle='" TEST_METADATA "';\0\0\0\0" + +#define ICY_DATA \ + "aaaaaaaa" \ + "\x02" \ + ICY_METADATA \ + "bbbbbbbb" + +#define ICYCAPS "application/x-icy, metadata-interval = (int)8" + +#define SRC_CAPS "application/x-icy, metadata-interval = (int)[0, MAX]" +#define SINK_CAPS "ANY" + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (SRC_CAPS) + ); + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (SINK_CAPS) + ); + +static GstElement *icydemux; +static GstBus *bus; +GstPad *srcpad, *sinkpad; + +static GstStaticCaps typefind_caps = +GST_STATIC_CAPS ("application/octet-stream"); + +static gboolean fake_typefind_caps; /* FALSE */ + +static void +typefind_succeed (GstTypeFind * tf, gpointer private) +{ + if (fake_typefind_caps) { + gst_type_find_suggest (tf, GST_TYPE_FIND_MAXIMUM, + gst_static_caps_get (&typefind_caps)); + } +} + +static gboolean +test_event_func (GstPad * pad, GstObject * parent, GstEvent * event) +{ + GST_LOG_OBJECT (pad, "%s event: %" GST_PTR_FORMAT, + GST_EVENT_TYPE_NAME (event), event); + + /* a sink would post tag events as messages, so do the same here, + * esp. since we're polling on the bus waiting for TAG messages.. */ + if (GST_EVENT_TYPE (event) == GST_EVENT_TAG) { + GstTagList *taglist; + + gst_event_parse_tag (event, &taglist); + + gst_bus_post (bus, gst_message_new_tag (GST_OBJECT (pad), + gst_tag_list_copy (taglist))); + } + + gst_event_unref (event); + return TRUE; +} + +static void +icydemux_found_pad (GstElement * src, GstPad * pad, gpointer data) +{ + GST_DEBUG ("got new pad %" GST_PTR_FORMAT, pad); + + /* Turns out that this asserts a refcount which is wrong for this + * case (adding the pad from a pad-added callback), so just do the same + * thing inline... */ + /* sinkpad = gst_check_setup_sink_pad (icydemux, &sinktemplate, NULL); */ + sinkpad = gst_pad_new_from_static_template (&sinktemplate, "sink"); + fail_if (sinkpad == NULL, "Couldn't create sinkpad"); + srcpad = gst_element_get_static_pad (icydemux, "src"); + fail_if (srcpad == NULL, "Failed to get srcpad from icydemux"); + gst_pad_set_chain_function (sinkpad, gst_check_chain_func); + gst_pad_set_event_function (sinkpad, test_event_func); + + GST_DEBUG ("checking srcpad %p refcount", srcpad); + /* 1 from element, 1 from signal, 1 from us */ + ASSERT_OBJECT_REFCOUNT (srcpad, "srcpad", 3); + + GST_DEBUG ("linking srcpad"); + fail_unless (gst_pad_link (srcpad, sinkpad) == GST_PAD_LINK_OK, + "Failed to link pads"); + gst_object_unref (srcpad); + + gst_pad_set_active (sinkpad, TRUE); +} + +static GstElement * +create_icydemux (void) +{ + icydemux = gst_check_setup_element ("icydemux"); + srcpad = gst_check_setup_src_pad (icydemux, &srctemplate); + + gst_pad_set_active (srcpad, TRUE); + + g_signal_connect (icydemux, "pad-added", G_CALLBACK (icydemux_found_pad), + NULL); + + bus = gst_bus_new (); + gst_element_set_bus (icydemux, bus); + + fail_unless (gst_element_set_state (icydemux, GST_STATE_PLAYING) != + GST_STATE_CHANGE_FAILURE, "could not set to playing"); + + return icydemux; +} + +static void +cleanup_icydemux (void) +{ + gst_bus_set_flushing (bus, TRUE); + gst_object_unref (bus); + bus = NULL; + + gst_check_teardown_src_pad (icydemux); + if (sinkpad) + gst_check_teardown_sink_pad (icydemux); + gst_check_teardown_element (icydemux); + + srcpad = NULL; + sinkpad = NULL; + icydemux = NULL; +} + +static void +push_data (const guint8 * data, int len, gint64 offset) +{ + GstFlowReturn res; + GstBuffer *buffer = gst_buffer_new_and_alloc (len); + + gst_buffer_fill (buffer, 0, data, len); + + GST_BUFFER_OFFSET (buffer) = offset; + + res = gst_pad_push (srcpad, buffer); + + fail_unless (res == GST_FLOW_OK, "Failed pushing buffer: %d", res); +} + +GST_START_TEST (test_demux) +{ + GstMessage *message; + GstTagList *tags; + const GValue *tag_val; + const gchar *tag; + GstCaps *caps; + + fail_unless (gst_type_find_register (NULL, "success", GST_RANK_PRIMARY, + typefind_succeed, NULL, gst_static_caps_get (&typefind_caps), NULL, + NULL)); + + fake_typefind_caps = TRUE; + + caps = gst_caps_from_string (ICYCAPS); + + create_icydemux (); + gst_check_setup_events (srcpad, icydemux, caps, GST_FORMAT_TIME); + + push_data ((guint8 *) ICY_DATA, sizeof (ICY_DATA), -1); + + message = gst_bus_poll (bus, GST_MESSAGE_TAG, -1); + fail_unless (message != NULL); + + gst_message_parse_tag (message, &tags); + fail_unless (tags != NULL); + + tag_val = gst_tag_list_get_value_index (tags, GST_TAG_TITLE, 0); + fail_unless (tag_val != NULL); + + tag = g_value_get_string (tag_val); + fail_unless (tag != NULL); + + fail_unless_equals_string (TEST_METADATA, (char *) tag); + + gst_tag_list_unref (tags); + gst_message_unref (message); + gst_caps_unref (caps); + + cleanup_icydemux (); + + fake_typefind_caps = FALSE; +} + +GST_END_TEST; + +/* run this test first before the custom typefind function is set up */ +GST_START_TEST (test_first_buf_offset_when_merged_for_typefinding) +{ + const guint8 buf1[] = { 'M' }; + const guint8 buf2[] = { 'P', '+', 0xff, 0xfb, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + GstCaps *icy_caps; + GstPad *icy_srcpad; + + fake_typefind_caps = FALSE; + + create_icydemux (); + + icy_caps = gst_caps_from_string (ICYCAPS); + + gst_check_setup_events (srcpad, icydemux, icy_caps, GST_FORMAT_TIME); + + push_data (buf1, G_N_ELEMENTS (buf1), 0); + + /* one byte isn't really enough for typefinding, can't have a srcpad yet */ + fail_unless (gst_element_get_static_pad (icydemux, "src") == NULL); + + push_data (buf2, G_N_ELEMENTS (buf2), -1); + + /* should have been enough to create a audio/x-musepack source pad .. */ + icy_srcpad = gst_element_get_static_pad (icydemux, "src"); + fail_unless (icy_srcpad != NULL); + gst_object_unref (icy_srcpad); + + fail_unless (g_list_length (buffers) > 0); + + /* first buffer should have offset 0 even after it was merged with 2nd buf */ + fail_unless (GST_BUFFER_OFFSET (GST_BUFFER_CAST (buffers->data)) == 0); + + gst_caps_unref (icy_caps); + + cleanup_icydemux (); +} + +GST_END_TEST; + +GST_START_TEST (test_not_negotiated) +{ + GstBuffer *buf; + GstSegment segment; + + create_icydemux (); + + gst_segment_init (&segment, GST_FORMAT_BYTES); + gst_pad_push_event (srcpad, gst_event_new_stream_start ("test")); + gst_pad_push_event (srcpad, gst_event_new_segment (&segment)); + + buf = gst_buffer_new_and_alloc (0); + GST_BUFFER_OFFSET (buf) = 0; + + fail_unless_equals_int (gst_pad_push (srcpad, buf), GST_FLOW_NOT_NEGOTIATED); + buf = NULL; + + cleanup_icydemux (); +} + +GST_END_TEST; + +static Suite * +icydemux_suite (void) +{ + Suite *s = suite_create ("icydemux"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_demux); + tcase_add_test (tc_chain, test_first_buf_offset_when_merged_for_typefinding); + tcase_add_test (tc_chain, test_not_negotiated); + + return s; +} + +GST_CHECK_MAIN (icydemux) diff --git a/tests/check/elements/id3demux.c b/tests/check/elements/id3demux.c new file mode 100644 index 0000000..17b524d --- /dev/null +++ b/tests/check/elements/id3demux.c @@ -0,0 +1,283 @@ +/* GStreamer unit tests for id3demux + * + * Copyright (C) 2007 Tim-Philipp Müller <tim centricular net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <gst/check/gstcheck.h> + +#include <gst/gst.h> + +typedef void (CheckTagsFunc) (const GstTagList * tags, const gchar * file); + +static GstBusSyncReply +error_cb (GstBus * bus, GstMessage * msg, gpointer user_data) +{ + if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) { + const gchar *file = (const gchar *) user_data; + GError *err = NULL; + gchar *dbg = NULL; + + gst_message_parse_error (msg, &err, &dbg); + g_error ("ERROR for %s: %s\n%s\n", file, err->message, dbg); + } + + return GST_BUS_PASS; +} + +static GstTagList * +read_tags_from_file (const gchar * file, gboolean push_mode) +{ + GstStateChangeReturn state_ret; + GstTagList *tags = NULL; + GstMessage *msg; + GstElement *src, *sep, *sink, *id3demux, *pipeline; + GstBus *bus; + gchar *path; + + pipeline = gst_pipeline_new ("pipeline"); + fail_unless (pipeline != NULL, "Failed to create pipeline!"); + + bus = gst_element_get_bus (pipeline); + + /* kids, don't use a sync handler for this at home, really; we do because + * we just want to abort and nothing else */ + gst_bus_set_sync_handler (bus, error_cb, (gpointer) file, NULL); + + src = gst_element_factory_make ("filesrc", "filesrc"); + fail_unless (src != NULL, "Failed to create 'filesrc' element!"); + + if (push_mode) { + sep = gst_element_factory_make ("queue", "queue"); + fail_unless (sep != NULL, "Failed to create 'queue' element"); + } else { + sep = gst_element_factory_make ("identity", "identity"); + fail_unless (sep != NULL, "Failed to create 'identity' element"); + } + + id3demux = gst_element_factory_make ("id3demux", "id3demux"); + fail_unless (id3demux != NULL, "Failed to create 'id3demux' element!"); + + sink = gst_element_factory_make ("fakesink", "fakesink"); + fail_unless (sink != NULL, "Failed to create 'fakesink' element!"); + + gst_bin_add_many (GST_BIN (pipeline), src, sep, id3demux, sink, NULL); + + fail_unless (gst_element_link (src, sep)); + fail_unless (gst_element_link (sep, id3demux)); + fail_unless (gst_element_link (id3demux, sink)); + + path = g_build_filename (GST_TEST_FILES_PATH, file, NULL); + GST_LOG ("reading file '%s'", path); + g_object_set (src, "location", path, NULL); + + state_ret = gst_element_set_state (pipeline, GST_STATE_PAUSED); + fail_unless (state_ret != GST_STATE_CHANGE_FAILURE); + + if (state_ret == GST_STATE_CHANGE_ASYNC) { + GST_LOG ("waiting for pipeline to reach PAUSED state"); + state_ret = gst_element_get_state (pipeline, NULL, NULL, -1); + fail_unless_equals_int (state_ret, GST_STATE_CHANGE_SUCCESS); + } + + GST_LOG ("PAUSED, let's retrieve our tags"); + + msg = gst_bus_poll (bus, GST_MESSAGE_TAG, -1); + fail_unless (msg != NULL, "Expected TAG message on bus! (%s)", file); + + gst_message_parse_tag (msg, &tags); + fail_unless (tags != NULL, "TAG message did not contain taglist! (%s)", file); + + gst_message_unref (msg); + gst_object_unref (bus); + + fail_unless_equals_int (gst_element_set_state (pipeline, GST_STATE_NULL), + GST_STATE_CHANGE_SUCCESS); + gst_object_unref (pipeline); + + g_free (path); + + GST_INFO ("%s: tags = %" GST_PTR_FORMAT, file, tags); + return tags; +} + +static void +run_check_for_file (const gchar * filename, CheckTagsFunc * check_func) +{ + GstTagList *tags; + + /* first, pull-based */ + tags = read_tags_from_file (filename, FALSE); + fail_unless (tags != NULL, "Failed to extract tags from '%s'", filename); + check_func (tags, filename); + gst_tag_list_unref (tags); + + /* FIXME: need to fix id3demux for short content in push mode */ +#if 0 + /* now try push-based */ + tags = read_tags_from_file (filename, TRUE); + fail_unless (tags != NULL, "Failed to extract tags from '%s'", filename); + check_func (tags, filename); + gst_tag_list_unref (tags); +#endif +} + +static void +check_date_1977_06_23 (const GstTagList * tags, const gchar * file) +{ + GstDateTime *date = NULL; + + gst_tag_list_get_date_time (tags, GST_TAG_DATE_TIME, &date); + fail_unless (date != NULL, + "Tags from %s should contain a GST_TAG_DATE_TIME tag"); + fail_unless_equals_int (gst_date_time_get_year (date), 1977); + fail_unless_equals_int (gst_date_time_get_month (date), 6); + fail_unless_equals_int (gst_date_time_get_day (date), 23); + gst_date_time_unref (date); +} + +GST_START_TEST (test_tdat_tyer) +{ + run_check_for_file ("id3-407349-1.tag", check_date_1977_06_23); + run_check_for_file ("id3-407349-2.tag", check_date_1977_06_23); +} + +GST_END_TEST; + +static void +check_wcop (const GstTagList * tags, const gchar * file) +{ + gchar *copyright = NULL; + gchar *uri = NULL; + + fail_unless (gst_tag_list_get_string (tags, GST_TAG_LICENSE_URI, &uri)); + fail_unless (uri != NULL); + fail_unless_equals_string (uri, + "http://creativecommons.org/licenses/by/3.0/"); + g_free (uri); + + fail_unless (gst_tag_list_get_string (tags, GST_TAG_COPYRIGHT, ©right)); + fail_unless (copyright != NULL); + fail_unless_equals_string (copyright, + " Steadman. Licensed to the public under http://creativecommons.org/licenses/by/3.0/ verify at http://test.com"); + g_free (copyright); +} + +GST_START_TEST (test_wcop) +{ + run_check_for_file ("id3-447000-wcop.tag", check_wcop); +} + +GST_END_TEST; + +static void +check_unsync_v23 (const GstTagList * tags, const gchar * file) +{ + gchar *album = NULL; + gchar *title = NULL; + gchar *artist = NULL; + + fail_unless (gst_tag_list_get_string (tags, GST_TAG_TITLE, &title)); + fail_unless (title != NULL); + fail_unless_equals_string (title, "ARTIST"); /* sic */ + g_free (title); + + fail_unless (gst_tag_list_get_string (tags, GST_TAG_ALBUM, &album)); + fail_unless (album != NULL); + fail_unless_equals_string (album, "Album"); + g_free (album); + + fail_unless (gst_tag_list_get_string (tags, GST_TAG_ARTIST, &artist)); + fail_unless (artist != NULL); + fail_unless_equals_string (artist, "藝人"); + g_free (artist); +} + +GST_START_TEST (test_unsync_v23) +{ + run_check_for_file ("id3-577468-unsynced-tag.tag", check_unsync_v23); +} + +GST_END_TEST; + +static void +check_unsync_v24 (const GstTagList * tags, const gchar * file) +{ + const GValue *val; + GstSample *sample; + GstBuffer *buf; + gchar *album = NULL; + gchar *title = NULL; + gchar *artist = NULL; + GstMapInfo map; + + fail_unless (gst_tag_list_get_string (tags, GST_TAG_TITLE, &title)); + fail_unless (title != NULL); + fail_unless_equals_string (title, "Starlight"); + g_free (title); + + fail_unless (gst_tag_list_get_string (tags, GST_TAG_ALBUM, &album)); + fail_unless (album != NULL); + fail_unless_equals_string (album, "L'albumRockVol.4 CD1"); + g_free (album); + + fail_unless (gst_tag_list_get_string (tags, GST_TAG_ARTIST, &artist)); + fail_unless (artist != NULL); + fail_unless_equals_string (artist, "Muse"); + g_free (artist); + + val = gst_tag_list_get_value_index (tags, GST_TAG_IMAGE, 0); + fail_unless (val != NULL); + fail_unless (GST_VALUE_HOLDS_SAMPLE (val)); + sample = gst_value_get_sample (val); + fail_unless (sample != NULL); + fail_unless (gst_sample_get_caps (sample) != NULL); + buf = gst_sample_get_buffer (sample); + fail_unless (buf != NULL); + gst_buffer_map (buf, &map, GST_MAP_READ); + fail_unless_equals_int (map.size, 38022); + /* check for jpeg start/end markers */ + fail_unless_equals_int (map.data[0], 0xff); + fail_unless_equals_int (map.data[1], 0xd8); + fail_unless_equals_int (map.data[38020], 0xff); + fail_unless_equals_int (map.data[38021], 0xd9); + gst_buffer_unmap (buf, &map); +} + +GST_START_TEST (test_unsync_v24) +{ + run_check_for_file ("id3-588148-unsynced-v24.tag", check_unsync_v24); +} + +GST_END_TEST; + +static Suite * +id3demux_suite (void) +{ + Suite *s = suite_create ("id3demux"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_tdat_tyer); + tcase_add_test (tc_chain, test_wcop); + tcase_add_test (tc_chain, test_unsync_v23); + tcase_add_test (tc_chain, test_unsync_v24); + + return s; +} + +GST_CHECK_MAIN (id3demux) diff --git a/tests/check/elements/id3v2mux.c b/tests/check/elements/id3v2mux.c new file mode 100644 index 0000000..21869e6 --- /dev/null +++ b/tests/check/elements/id3v2mux.c @@ -0,0 +1,536 @@ +/* GStreamer + * + * unit test for the taglib-based id3v2mux element + * + * Copyright (C) 2006 Tim-Philipp Müller <tim centricular net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <gst/check/gstcheck.h> + +#include <gst/gst.h> +#include <string.h> + +#define TEST_ARTIST "Ar T\303\255st" +#define TEST_TITLE "M\303\274llermilch!" +#define TEST_ALBUM "Boom" +#define TEST_DATE g_date_new_dmy(1,1,2006) +#define TEST_TRACK_NUMBER 7 +#define TEST_TRACK_COUNT 19 +#define TEST_VOLUME_NUMBER 2 +#define TEST_VOLUME_COUNT 3 +#define TEST_TRACK_GAIN 1.45 +#define TEST_ALBUM_GAIN 0.78 +#define TEST_TRACK_PEAK 0.83 +#define TEST_ALBUM_PEAK 0.18 +#define TEST_BPM 113.0 + +/* for dummy mp3 frame sized MP3_FRAME_SIZE bytes, + * start: ff fb b0 44 00 00 08 00 00 4b 00 00 00 00 00 00 */ +static const guint8 mp3_dummyhdr[] = { 0xff, 0xfb, 0xb0, 0x44, 0x00, 0x00, + 0x08, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00 +}; + +#define MP3_FRAME_SIZE 626 + +/* the peak and gain values are stored pretty roughly, so check that they're + * within 2% of the expected value. + */ +#define fail_unless_sorta_equals_float(a, b) \ +G_STMT_START { \ + double first = a; \ + double second = b; \ + fail_unless(fabs (first - second) < (0.02 * fabs (first)), \ + "'" #a "' (%g) is not equal to '" #b "' (%g)", first, second); \ +} G_STMT_END; + + +static GstTagList * +test_taglib_id3mux_create_tags (guint32 mask) +{ + GstTagList *tags; + + tags = gst_tag_list_new_empty (); + + if (mask & (1 << 0)) { + gst_tag_list_add (tags, GST_TAG_MERGE_KEEP, + GST_TAG_ARTIST, TEST_ARTIST, NULL); + } + if (mask & (1 << 1)) { + gst_tag_list_add (tags, GST_TAG_MERGE_KEEP, + GST_TAG_TITLE, TEST_TITLE, NULL); + } + if (mask & (1 << 2)) { + gst_tag_list_add (tags, GST_TAG_MERGE_KEEP, + GST_TAG_ALBUM, TEST_ALBUM, NULL); + } + if (mask & (1 << 3)) { + GDate *date; + + date = TEST_DATE; + gst_tag_list_add (tags, GST_TAG_MERGE_KEEP, GST_TAG_DATE, date, NULL); + g_date_free (date); + } + if (mask & (1 << 4)) { + gst_tag_list_add (tags, GST_TAG_MERGE_KEEP, + GST_TAG_TRACK_NUMBER, TEST_TRACK_NUMBER, NULL); + } + if (mask & (1 << 5)) { + gst_tag_list_add (tags, GST_TAG_MERGE_KEEP, + GST_TAG_TRACK_COUNT, TEST_TRACK_COUNT, NULL); + } + if (mask & (1 << 6)) { + gst_tag_list_add (tags, GST_TAG_MERGE_KEEP, + GST_TAG_ALBUM_VOLUME_NUMBER, TEST_VOLUME_NUMBER, NULL); + } + if (mask & (1 << 7)) { + gst_tag_list_add (tags, GST_TAG_MERGE_KEEP, + GST_TAG_ALBUM_VOLUME_COUNT, TEST_VOLUME_COUNT, NULL); + } + if (mask & (1 << 8)) { + gst_tag_list_add (tags, GST_TAG_MERGE_KEEP, + GST_TAG_TRACK_GAIN, TEST_TRACK_GAIN, NULL); + } + if (mask & (1 << 9)) { + gst_tag_list_add (tags, GST_TAG_MERGE_KEEP, + GST_TAG_ALBUM_GAIN, TEST_ALBUM_GAIN, NULL); + } + if (mask & (1 << 10)) { + gst_tag_list_add (tags, GST_TAG_MERGE_KEEP, + GST_TAG_TRACK_PEAK, TEST_TRACK_PEAK, NULL); + } + if (mask & (1 << 11)) { + gst_tag_list_add (tags, GST_TAG_MERGE_KEEP, + GST_TAG_ALBUM_PEAK, TEST_ALBUM_PEAK, NULL); + } + if (mask & (1 << 12)) { + gst_tag_list_add (tags, GST_TAG_MERGE_KEEP, + GST_TAG_BEATS_PER_MINUTE, TEST_BPM, NULL); + } + if (mask & (1 << 13)) { + } + return tags; +} + +static gboolean +utf8_string_in_buf (GstBuffer * buf, const gchar * s) +{ + gint i, len; + GstMapInfo map; + + len = strlen (s); + gst_buffer_map (buf, &map, GST_MAP_READ); + for (i = 0; i < (map.size - len); ++i) { + if (memcmp (map.data + i, s, len) == 0) { + gst_buffer_unmap (buf, &map); + return TRUE; + } + } + gst_buffer_unmap (buf, &map); + + return FALSE; +} + +static void +test_taglib_id3mux_check_tag_buffer (GstBuffer * buf, guint32 mask) +{ + /* make sure our UTF-8 string hasn't been put into the tag as ISO-8859-1 */ + if (mask & (1 << 0)) { + fail_unless (utf8_string_in_buf (buf, TEST_ARTIST)); + } + /* make sure our UTF-8 string hasn't been put into the tag as ISO-8859-1 */ + if (mask & (1 << 1)) { + fail_unless (utf8_string_in_buf (buf, TEST_TITLE)); + } + /* make sure our UTF-8 string hasn't been put into the tag as ISO-8859-1 */ + if (mask & (1 << 2)) { + fail_unless (utf8_string_in_buf (buf, TEST_ALBUM)); + } +} + +static void +test_taglib_id3mux_check_tags (GstTagList * tags, guint32 mask) +{ + if (mask & (1 << 0)) { + gchar *s = NULL; + + fail_unless (gst_tag_list_get_string (tags, GST_TAG_ARTIST, &s)); + fail_unless (g_str_equal (s, TEST_ARTIST)); + g_free (s); + } + if (mask & (1 << 1)) { + gchar *s = NULL; + + fail_unless (gst_tag_list_get_string (tags, GST_TAG_TITLE, &s)); + fail_unless (g_str_equal (s, TEST_TITLE)); + g_free (s); + } + if (mask & (1 << 2)) { + gchar *s = NULL; + + fail_unless (gst_tag_list_get_string (tags, GST_TAG_ALBUM, &s)); + fail_unless (g_str_equal (s, TEST_ALBUM)); + g_free (s); + } + if (mask & (1 << 3)) { + GDate *shouldbe, *date = NULL; + + shouldbe = TEST_DATE; + fail_unless (gst_tag_list_get_date (tags, GST_TAG_DATE, &date)); + fail_unless (g_date_compare (shouldbe, date) == 0); + g_date_free (shouldbe); + g_date_free (date); + } + if (mask & (1 << 4)) { + guint num; + + fail_unless (gst_tag_list_get_uint (tags, GST_TAG_TRACK_NUMBER, &num)); + fail_unless (num == TEST_TRACK_NUMBER); + } + if (mask & (1 << 5)) { + guint count; + + fail_unless (gst_tag_list_get_uint (tags, GST_TAG_TRACK_COUNT, &count)); + fail_unless (count == TEST_TRACK_COUNT); + } + if (mask & (1 << 6)) { + guint num; + + fail_unless (gst_tag_list_get_uint (tags, GST_TAG_ALBUM_VOLUME_NUMBER, + &num)); + fail_unless (num == TEST_VOLUME_NUMBER); + } + if (mask & (1 << 7)) { + guint count; + + fail_unless (gst_tag_list_get_uint (tags, GST_TAG_ALBUM_VOLUME_COUNT, + &count)); + fail_unless (count == TEST_VOLUME_COUNT); + } + if (mask & (1 << 8)) { + gdouble gain; + + fail_unless (gst_tag_list_get_double (tags, GST_TAG_TRACK_GAIN, &gain)); + fail_unless_sorta_equals_float (gain, TEST_TRACK_GAIN); + } + if (mask & (1 << 9)) { + gdouble gain; + + fail_unless (gst_tag_list_get_double (tags, GST_TAG_ALBUM_GAIN, &gain)); + fail_unless_sorta_equals_float (gain, TEST_ALBUM_GAIN); + } + if (mask & (1 << 10)) { + gdouble peak; + + fail_unless (gst_tag_list_get_double (tags, GST_TAG_TRACK_PEAK, &peak)); + fail_unless_sorta_equals_float (peak, TEST_TRACK_PEAK); + } + if (mask & (1 << 11)) { + gdouble peak; + + fail_unless (gst_tag_list_get_double (tags, GST_TAG_ALBUM_PEAK, &peak)); + fail_unless_sorta_equals_float (peak, TEST_ALBUM_PEAK); + } + if (mask & (1 << 12)) { + gdouble bpm; + + fail_unless (gst_tag_list_get_double (tags, GST_TAG_BEATS_PER_MINUTE, + &bpm)); + fail_unless_sorta_equals_float (bpm, TEST_BPM); + } + if (mask & (1 << 13)) { + } +} + +static void +fill_mp3_buffer (GstElement * fakesrc, GstBuffer * buf, GstPad * pad, + guint64 * p_offset) +{ + gsize size; + + size = gst_buffer_get_size (buf); + + fail_unless (size == MP3_FRAME_SIZE); + + GST_LOG ("filling buffer with fake mp3 data, offset = %" G_GUINT64_FORMAT, + *p_offset); + + gst_buffer_fill (buf, 0, mp3_dummyhdr, sizeof (mp3_dummyhdr)); + +#if 0 + /* can't use gst_buffer_set_caps() here because the metadata isn't writable + * because of the extra refcounts taken by the signal emission mechanism; + * we know it's fine to use GST_BUFFER_CAPS() here though */ + GST_BUFFER_CAPS (buf) = gst_caps_new_simple ("audio/mpeg", "mpegversion", + G_TYPE_INT, 1, "layer", G_TYPE_INT, 3, NULL); +#endif + + GST_BUFFER_OFFSET (buf) = *p_offset; + *p_offset += size; +} + +static void +got_buffer (GstElement * fakesink, GstBuffer * buf, GstPad * pad, + GstBuffer ** p_buf) +{ + gint64 off; + GstMapInfo map; + + off = GST_BUFFER_OFFSET (buf); + + gst_buffer_map (buf, &map, GST_MAP_READ); + + GST_LOG ("size=%" G_GSIZE_FORMAT ", offset=%" G_GINT64_FORMAT, map.size, off); + + fail_unless (GST_BUFFER_OFFSET_IS_VALID (buf)); + + if (*p_buf == NULL || (off + map.size) > gst_buffer_get_size (*p_buf)) { + GstBuffer *newbuf; + + /* not very elegant, but who cares */ + newbuf = gst_buffer_new_and_alloc (off + map.size); + if (*p_buf) { + GstMapInfo pmap; + + gst_buffer_map (*p_buf, &pmap, GST_MAP_READ); + gst_buffer_fill (newbuf, 0, pmap.data, pmap.size); + gst_buffer_unmap (*p_buf, &pmap); + } + gst_buffer_fill (newbuf, off, map.data, map.size); + + if (*p_buf) + gst_buffer_unref (*p_buf); + *p_buf = newbuf; + } else { + gst_buffer_fill (*p_buf, off, map.data, map.size); + } + gst_buffer_unmap (buf, &map); +} + +static void +test_taglib_id3mux_check_output_buffer (GstBuffer * buf) +{ + GstMapInfo map; + guint off; + + gst_buffer_map (buf, &map, GST_MAP_READ); + g_assert (map.size % MP3_FRAME_SIZE == 0); + + for (off = 0; off < map.size; off += MP3_FRAME_SIZE) { + fail_unless (memcmp (map.data + off, mp3_dummyhdr, + sizeof (mp3_dummyhdr)) == 0); + } + gst_buffer_unmap (buf, &map); +} + +static void +identity_cb (GstElement * identity, GstBuffer * buf, GstBuffer ** p_tagbuf) +{ + if (*p_tagbuf == NULL) { + *p_tagbuf = gst_buffer_ref (buf); + } +} + +static void +test_taglib_id3mux_with_tags (GstTagList * tags, guint32 mask) +{ + GstMessage *msg; + GstTagList *tags_read = NULL; + GstElement *pipeline, *id3mux, *id3demux, *fakesrc, *identity, *fakesink; + GstBus *bus; + guint64 offset; + GstBuffer *outbuf = NULL; + GstBuffer *tagbuf = NULL; + GstStateChangeReturn state_result; + + pipeline = gst_pipeline_new ("pipeline"); + g_assert (pipeline != NULL); + + fakesrc = gst_element_factory_make ("fakesrc", "fakesrc"); + g_assert (fakesrc != NULL); + + id3mux = gst_element_factory_make ("id3v2mux", "id3v2mux"); + g_assert (id3mux != NULL); + + identity = gst_element_factory_make ("identity", "identity"); + g_assert (identity != NULL); + + id3demux = gst_element_factory_make ("id3demux", "id3demux"); + g_assert (id3demux != NULL); + + fakesink = gst_element_factory_make ("fakesink", "fakesink"); + g_assert (fakesink != NULL); + + /* set up sink */ + outbuf = NULL; + g_object_set (fakesink, "signal-handoffs", TRUE, NULL); + g_signal_connect (fakesink, "handoff", G_CALLBACK (got_buffer), &outbuf); + + gst_bin_add (GST_BIN (pipeline), fakesrc); + gst_bin_add (GST_BIN (pipeline), id3mux); + gst_bin_add (GST_BIN (pipeline), identity); + gst_bin_add (GST_BIN (pipeline), id3demux); + gst_bin_add (GST_BIN (pipeline), fakesink); + + gst_tag_setter_merge_tags (GST_TAG_SETTER (id3mux), tags, + GST_TAG_MERGE_APPEND); + + gst_element_link_many (fakesrc, id3mux, identity, id3demux, fakesink, NULL); + + /* set up source */ + g_object_set (fakesrc, "signal-handoffs", TRUE, "can-activate-pull", FALSE, + "filltype", 2, "sizetype", 2, "sizemax", MP3_FRAME_SIZE, + "num-buffers", 16, NULL); + + offset = 0; + g_signal_connect (fakesrc, "handoff", G_CALLBACK (fill_mp3_buffer), &offset); + + /* set up identity to catch tag buffer */ + g_signal_connect (identity, "handoff", G_CALLBACK (identity_cb), &tagbuf); + + GST_LOG ("setting and getting state ..."); + gst_element_set_state (pipeline, GST_STATE_PLAYING); + state_result = gst_element_get_state (pipeline, NULL, NULL, -1); + fail_unless (state_result == GST_STATE_CHANGE_SUCCESS, + "Unexpected result from get_state(). Expected success, got %d", + state_result); + + bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); + + GST_LOG ("Waiting for tag ..."); + msg = + gst_bus_poll (bus, GST_MESSAGE_TAG | GST_MESSAGE_EOS | GST_MESSAGE_ERROR, + -1); + if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) { + GError *err; + gchar *dbg; + + gst_message_parse_error (msg, &err, &dbg); + g_printerr ("ERROR from element %s: %s\n%s\n", + GST_OBJECT_NAME (msg->src), err->message, GST_STR_NULL (dbg)); + g_error_free (err); + g_free (dbg); + } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_EOS) { + g_printerr ("EOS message, but were waiting for TAGS!\n"); + } + fail_unless (msg->type == GST_MESSAGE_TAG); + + gst_message_parse_tag (msg, &tags_read); + gst_message_unref (msg); + + GST_LOG ("Got tags: %" GST_PTR_FORMAT, tags_read); + test_taglib_id3mux_check_tags (tags_read, mask); + gst_tag_list_unref (tags_read); + + fail_unless (tagbuf != NULL); + test_taglib_id3mux_check_tag_buffer (tagbuf, mask); + gst_buffer_unref (tagbuf); + + GST_LOG ("Waiting for EOS ..."); + msg = gst_bus_poll (bus, GST_MESSAGE_EOS | GST_MESSAGE_ERROR, -1); + if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) { + GError *err; + gchar *dbg; + + gst_message_parse_error (msg, &err, &dbg); + g_printerr ("ERROR from element %s: %s\n%s\n", + GST_OBJECT_NAME (msg->src), err->message, GST_STR_NULL (dbg)); + g_error_free (err); + g_free (dbg); + } + fail_unless (msg->type == GST_MESSAGE_EOS); + gst_message_unref (msg); + + gst_object_unref (bus); + + GST_LOG ("Got EOS, shutting down ..."); + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_object_unref (pipeline); + + test_taglib_id3mux_check_output_buffer (outbuf); + gst_buffer_unref (outbuf); + + GST_LOG ("Done"); +} + +GST_START_TEST (test_id3v2mux) +{ + GstTagList *tags; + gint i; + + g_random_set_seed (247166295); + + /* internal consistency check */ + tags = test_taglib_id3mux_create_tags (0xFFFFFFFF); + test_taglib_id3mux_check_tags (tags, 0xFFFFFFFF); + gst_tag_list_unref (tags); + + /* now the real tests */ + for (i = 0; i < 50; ++i) { + guint32 mask; + + mask = g_random_int (); + GST_LOG ("tag mask = %08x (i=%d)", mask, i); + + if (mask == 0) + continue; + + /* create tags */ + tags = test_taglib_id3mux_create_tags (mask); + GST_LOG ("tags for mask %08x = %" GST_PTR_FORMAT, mask, tags); + + /* double-check for internal consistency */ + test_taglib_id3mux_check_tags (tags, mask); + + /* test with pipeline */ + test_taglib_id3mux_with_tags (tags, mask); + + /* free tags */ + gst_tag_list_unref (tags); + } +} + +GST_END_TEST; + +static Suite * +id3v2mux_suite (void) +{ + Suite *s = suite_create ("id3v2mux"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_id3v2mux); + + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = id3v2mux_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/imagefreeze.c b/tests/check/elements/imagefreeze.c new file mode 100644 index 0000000..cfeff37 --- /dev/null +++ b/tests/check/elements/imagefreeze.c @@ -0,0 +1,586 @@ +/* GStreamer + * Copyright (C) 2010 Sebastian Dröge <sebastian.droege@collabora.co.uk> + * + * 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 <string.h> + +#include <gst/check/gstcheck.h> +#include <gst/video/video.h> + +static gboolean +bus_handler (GstBus * bus, GstMessage * message, gpointer data) +{ + GMainLoop *loop = (GMainLoop *) data; + + switch (message->type) { + case GST_MESSAGE_EOS: + g_main_loop_quit (loop); + break; + case GST_MESSAGE_WARNING: + case GST_MESSAGE_ERROR:{ + GError *gerror; + gchar *debug; + + if (message->type == GST_MESSAGE_WARNING) + gst_message_parse_warning (message, &gerror, &debug); + else + gst_message_parse_error (message, &gerror, &debug); + g_error ("error from %s: %s (%s)\n", + GST_ELEMENT_NAME (GST_MESSAGE_SRC (message)), gerror->message, + GST_STR_NULL (debug)); + g_error_free (gerror); + g_free (debug); + g_main_loop_quit (loop); + break; + } + default: + break; + } + + return TRUE; +} + +static GstElement * +setup_imagefreeze (const GstCaps * caps1, const GstCaps * caps2, + GCallback sink_handoff, gpointer sink_handoff_data) +{ + GstElement *pipeline; + GstElement *videotestsrc, *capsfilter1, *imagefreeze, *capsfilter2, *fakesink; + + pipeline = gst_pipeline_new ("pipeline"); + fail_unless (pipeline != NULL); + + videotestsrc = gst_element_factory_make ("videotestsrc", "src"); + fail_unless (videotestsrc != NULL); + g_object_set (videotestsrc, "num-buffers", 1, NULL); + + capsfilter1 = gst_element_factory_make ("capsfilter", "filter1"); + fail_unless (capsfilter1 != NULL); + g_object_set (capsfilter1, "caps", caps1, NULL); + + imagefreeze = gst_element_factory_make ("imagefreeze", "freeze"); + fail_unless (imagefreeze != NULL); + + capsfilter2 = gst_element_factory_make ("capsfilter", "filter2"); + fail_unless (capsfilter2 != NULL); + g_object_set (capsfilter2, "caps", caps2, NULL); + + fakesink = gst_element_factory_make ("fakesink", "sink"); + fail_unless (fakesink != NULL); + g_object_set (fakesink, "signal-handoffs", TRUE, "async", FALSE, NULL); + + if (sink_handoff) + g_signal_connect (fakesink, "handoff", sink_handoff, sink_handoff_data); + + gst_bin_add_many (GST_BIN (pipeline), videotestsrc, capsfilter1, imagefreeze, + capsfilter2, fakesink, NULL); + + fail_unless (gst_element_link_pads (videotestsrc, "src", capsfilter1, + "sink")); + fail_unless (gst_element_link_pads (capsfilter1, "src", imagefreeze, "sink")); + fail_unless (gst_element_link_pads (imagefreeze, "src", capsfilter2, "sink")); + fail_unless (gst_element_link_pads (capsfilter2, "src", fakesink, "sink")); + + return pipeline; +} + +static void +sink_handoff_cb_0_1 (GstElement * object, GstBuffer * buffer, GstPad * pad, + gpointer user_data) +{ + guint *n_buffers = (guint *) user_data; + + if (*n_buffers == G_MAXUINT) + return; + + fail_unless_equals_uint64 (GST_BUFFER_TIMESTAMP (buffer), 0); + fail_unless_equals_uint64 (GST_BUFFER_DURATION (buffer), GST_CLOCK_TIME_NONE); + fail_unless_equals_uint64 (GST_BUFFER_OFFSET (buffer), 0); + fail_unless_equals_uint64 (GST_BUFFER_OFFSET_END (buffer), 1); + + *n_buffers = *n_buffers + 1; +} + +GST_START_TEST (test_imagefreeze_0_1) +{ + GstElement *pipeline; + GstCaps *caps1, *caps2; + GstBus *bus; + GMainLoop *loop; + guint n_buffers = G_MAXUINT; + guint bus_watch = 0; + GstVideoInfo i1, i2; + + gst_video_info_init (&i1); + gst_video_info_set_format (&i1, GST_VIDEO_FORMAT_xRGB, 640, 480); + i1.fps_n = 25; + i1.fps_d = 1; + caps1 = gst_video_info_to_caps (&i1); + + gst_video_info_init (&i2); + gst_video_info_set_format (&i2, GST_VIDEO_FORMAT_xRGB, 640, 480); + caps2 = gst_video_info_to_caps (&i2); + + pipeline = + setup_imagefreeze (caps1, caps2, G_CALLBACK (sink_handoff_cb_0_1), + &n_buffers); + + loop = g_main_loop_new (NULL, TRUE); + fail_unless (loop != NULL); + + bus = gst_element_get_bus (pipeline); + fail_unless (bus != NULL); + bus_watch = gst_bus_add_watch (bus, bus_handler, loop); + gst_object_unref (bus); + + n_buffers = 0; + fail_unless_equals_int (gst_element_set_state (pipeline, GST_STATE_PLAYING), + GST_STATE_CHANGE_SUCCESS); + + g_main_loop_run (loop); + + fail_unless_equals_int (n_buffers, 1); + + gst_element_set_state (pipeline, GST_STATE_NULL); + + gst_object_unref (pipeline); + g_main_loop_unref (loop); + gst_caps_unref (caps1); + gst_caps_unref (caps2); + g_source_remove (bus_watch); +} + +GST_END_TEST; + +static void +sink_handoff_cb_25_1_0ms_400ms (GstElement * object, GstBuffer * buffer, + GstPad * pad, gpointer user_data) +{ + guint *n_buffers = (guint *) user_data; + + if (*n_buffers == G_MAXUINT) + return; + + fail_unless_equals_uint64 (GST_BUFFER_TIMESTAMP (buffer), + *n_buffers * 40 * GST_MSECOND); + fail_unless_equals_uint64 (GST_BUFFER_DURATION (buffer), 40 * GST_MSECOND); + fail_unless_equals_uint64 (GST_BUFFER_OFFSET (buffer), *n_buffers); + fail_unless_equals_uint64 (GST_BUFFER_OFFSET_END (buffer), *n_buffers + 1); + + *n_buffers = *n_buffers + 1; +} + +GST_START_TEST (test_imagefreeze_25_1_0ms_400ms) +{ + GstElement *pipeline; + GstCaps *caps1, *caps2; + GstBus *bus; + GMainLoop *loop; + guint n_buffers = G_MAXUINT; + guint bus_watch = 0; + GstVideoInfo i1, i2; + + gst_video_info_init (&i1); + gst_video_info_set_format (&i1, GST_VIDEO_FORMAT_xRGB, 640, 480); + i1.fps_n = 25; + i1.fps_d = 1; + caps1 = gst_video_info_to_caps (&i1); + + gst_video_info_init (&i2); + gst_video_info_set_format (&i2, GST_VIDEO_FORMAT_xRGB, 640, 480); + i2.fps_n = 25; + i2.fps_d = 1; + caps2 = gst_video_info_to_caps (&i2); + + pipeline = + setup_imagefreeze (caps1, caps2, + G_CALLBACK (sink_handoff_cb_25_1_0ms_400ms), &n_buffers); + + loop = g_main_loop_new (NULL, TRUE); + fail_unless (loop != NULL); + + bus = gst_element_get_bus (pipeline); + fail_unless (bus != NULL); + bus_watch = gst_bus_add_watch (bus, bus_handler, loop); + gst_object_unref (bus); + + fail_unless_equals_int (gst_element_set_state (pipeline, GST_STATE_PAUSED), + GST_STATE_CHANGE_SUCCESS); + + fail_unless (gst_element_seek (pipeline, 1.0, GST_FORMAT_TIME, + GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_SET, + 400 * GST_MSECOND)); + + n_buffers = 0; + + fail_unless_equals_int (gst_element_set_state (pipeline, GST_STATE_PLAYING), + GST_STATE_CHANGE_SUCCESS); + + g_main_loop_run (loop); + + fail_unless_equals_int (n_buffers, 10); + + gst_element_set_state (pipeline, GST_STATE_NULL); + + gst_object_unref (pipeline); + g_main_loop_unref (loop); + gst_caps_unref (caps1); + gst_caps_unref (caps2); + g_source_remove (bus_watch); +} + +GST_END_TEST; + +static void +sink_handoff_cb_25_1_200ms_400ms (GstElement * object, GstBuffer * buffer, + GstPad * pad, gpointer user_data) +{ + guint *n_buffers = (guint *) user_data; + + if (*n_buffers == G_MAXUINT) + return; + + fail_unless_equals_uint64 (GST_BUFFER_TIMESTAMP (buffer), + 200 * GST_MSECOND + *n_buffers * 40 * GST_MSECOND); + fail_unless_equals_uint64 (GST_BUFFER_DURATION (buffer), 40 * GST_MSECOND); + fail_unless_equals_uint64 (GST_BUFFER_OFFSET (buffer), 5 + *n_buffers); + fail_unless_equals_uint64 (GST_BUFFER_OFFSET_END (buffer), + 5 + *n_buffers + 1); + + *n_buffers = *n_buffers + 1; +} + +GST_START_TEST (test_imagefreeze_25_1_200ms_400ms) +{ + GstElement *pipeline; + GstCaps *caps1, *caps2; + GstBus *bus; + GMainLoop *loop; + guint n_buffers = G_MAXUINT; + guint bus_watch = 0; + GstVideoInfo i1, i2; + + gst_video_info_init (&i1); + gst_video_info_set_format (&i1, GST_VIDEO_FORMAT_xRGB, 640, 480); + i1.fps_n = 25; + i1.fps_d = 1; + caps1 = gst_video_info_to_caps (&i1); + + gst_video_info_init (&i2); + gst_video_info_set_format (&i2, GST_VIDEO_FORMAT_xRGB, 640, 480); + i2.fps_n = 25; + i2.fps_d = 1; + caps2 = gst_video_info_to_caps (&i2); + + pipeline = + setup_imagefreeze (caps1, caps2, + G_CALLBACK (sink_handoff_cb_25_1_200ms_400ms), &n_buffers); + + loop = g_main_loop_new (NULL, TRUE); + fail_unless (loop != NULL); + + bus = gst_element_get_bus (pipeline); + fail_unless (bus != NULL); + bus_watch = gst_bus_add_watch (bus, bus_handler, loop); + gst_object_unref (bus); + + fail_unless_equals_int (gst_element_set_state (pipeline, GST_STATE_PAUSED), + GST_STATE_CHANGE_SUCCESS); + + fail_unless (gst_element_seek (pipeline, 1.0, GST_FORMAT_TIME, + GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, 200 * GST_MSECOND, + GST_SEEK_TYPE_SET, 400 * GST_MSECOND)); + + n_buffers = 0; + + fail_unless_equals_int (gst_element_set_state (pipeline, GST_STATE_PLAYING), + GST_STATE_CHANGE_SUCCESS); + + g_main_loop_run (loop); + + fail_unless_equals_int (n_buffers, 5); + + gst_element_set_state (pipeline, GST_STATE_NULL); + + gst_object_unref (pipeline); + g_main_loop_unref (loop); + gst_caps_unref (caps1); + gst_caps_unref (caps2); + g_source_remove (bus_watch); +} + +GST_END_TEST; + +static void +sink_handoff_cb_25_1_400ms_0ms (GstElement * object, GstBuffer * buffer, + GstPad * pad, gpointer user_data) +{ + guint *n_buffers = (guint *) user_data; + + if (*n_buffers == G_MAXUINT) + return; + + fail_unless_equals_uint64 (GST_BUFFER_TIMESTAMP (buffer), + 400 * GST_MSECOND - (*n_buffers + 1) * 40 * GST_MSECOND); + fail_unless_equals_uint64 (GST_BUFFER_DURATION (buffer), 40 * GST_MSECOND); + fail_unless_equals_uint64 (GST_BUFFER_OFFSET (buffer), 10 - (*n_buffers + 1)); + fail_unless_equals_uint64 (GST_BUFFER_OFFSET_END (buffer), + 10 - (*n_buffers + 1) + 1); + + *n_buffers = *n_buffers + 1; +} + +GST_START_TEST (test_imagefreeze_25_1_400ms_0ms) +{ + GstElement *pipeline; + GstCaps *caps1, *caps2; + GstBus *bus; + GMainLoop *loop; + guint n_buffers = G_MAXUINT; + guint bus_watch = 0; + GstVideoInfo i1, i2; + + gst_video_info_init (&i1); + gst_video_info_set_format (&i1, GST_VIDEO_FORMAT_xRGB, 640, 480); + i1.fps_n = 25; + i1.fps_d = 1; + caps1 = gst_video_info_to_caps (&i1); + + gst_video_info_init (&i2); + gst_video_info_set_format (&i2, GST_VIDEO_FORMAT_xRGB, 640, 480); + i2.fps_n = 25; + i2.fps_d = 1; + caps2 = gst_video_info_to_caps (&i2); + + pipeline = + setup_imagefreeze (caps1, caps2, + G_CALLBACK (sink_handoff_cb_25_1_400ms_0ms), &n_buffers); + + loop = g_main_loop_new (NULL, TRUE); + fail_unless (loop != NULL); + + bus = gst_element_get_bus (pipeline); + fail_unless (bus != NULL); + bus_watch = gst_bus_add_watch (bus, bus_handler, loop); + gst_object_unref (bus); + + fail_unless_equals_int (gst_element_set_state (pipeline, GST_STATE_PAUSED), + GST_STATE_CHANGE_SUCCESS); + + fail_unless (gst_element_seek (pipeline, -1.0, GST_FORMAT_TIME, + GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_SET, + 400 * GST_MSECOND)); + + n_buffers = 0; + + fail_unless_equals_int (gst_element_set_state (pipeline, GST_STATE_PLAYING), + GST_STATE_CHANGE_SUCCESS); + + g_main_loop_run (loop); + + fail_unless_equals_int (n_buffers, 10); + + gst_element_set_state (pipeline, GST_STATE_NULL); + + gst_object_unref (pipeline); + g_main_loop_unref (loop); + gst_caps_unref (caps1); + gst_caps_unref (caps2); + g_source_remove (bus_watch); +} + +GST_END_TEST; + +static void +sink_handoff_cb_25_1_220ms_380ms (GstElement * object, GstBuffer * buffer, + GstPad * pad, gpointer user_data) +{ + guint *n_buffers = (guint *) user_data; + + if (*n_buffers == G_MAXUINT) + return; + + if (*n_buffers == 0) { + fail_unless_equals_uint64 (GST_BUFFER_TIMESTAMP (buffer), + 220 * GST_MSECOND); + fail_unless_equals_uint64 (GST_BUFFER_DURATION (buffer), 20 * GST_MSECOND); + } else if (*n_buffers == 4) { + fail_unless_equals_uint64 (GST_BUFFER_TIMESTAMP (buffer), + 360 * GST_MSECOND); + fail_unless_equals_uint64 (GST_BUFFER_DURATION (buffer), 20 * GST_MSECOND); + } else { + fail_unless_equals_uint64 (GST_BUFFER_TIMESTAMP (buffer), + 200 * GST_MSECOND + *n_buffers * 40 * GST_MSECOND); + fail_unless_equals_uint64 (GST_BUFFER_DURATION (buffer), 40 * GST_MSECOND); + } + + fail_unless_equals_uint64 (GST_BUFFER_OFFSET (buffer), 5 + *n_buffers); + fail_unless_equals_uint64 (GST_BUFFER_OFFSET_END (buffer), + 5 + *n_buffers + 1); + + *n_buffers = *n_buffers + 1; +} + +GST_START_TEST (test_imagefreeze_25_1_220ms_380ms) +{ + GstElement *pipeline; + GstCaps *caps1, *caps2; + GstBus *bus; + GMainLoop *loop; + guint n_buffers = G_MAXUINT; + guint bus_watch = 0; + GstVideoInfo i1, i2; + + gst_video_info_init (&i1); + gst_video_info_set_format (&i1, GST_VIDEO_FORMAT_xRGB, 640, 480); + i1.fps_n = 25; + i1.fps_d = 1; + caps1 = gst_video_info_to_caps (&i1); + + gst_video_info_init (&i2); + gst_video_info_set_format (&i2, GST_VIDEO_FORMAT_xRGB, 640, 480); + i2.fps_n = 25; + i2.fps_d = 1; + caps2 = gst_video_info_to_caps (&i2); + + pipeline = + setup_imagefreeze (caps1, caps2, + G_CALLBACK (sink_handoff_cb_25_1_220ms_380ms), &n_buffers); + + loop = g_main_loop_new (NULL, TRUE); + fail_unless (loop != NULL); + + bus = gst_element_get_bus (pipeline); + fail_unless (bus != NULL); + bus_watch = gst_bus_add_watch (bus, bus_handler, loop); + gst_object_unref (bus); + + fail_unless_equals_int (gst_element_set_state (pipeline, GST_STATE_PAUSED), + GST_STATE_CHANGE_SUCCESS); + + fail_unless (gst_element_seek (pipeline, 1.0, GST_FORMAT_TIME, + GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, 220 * GST_MSECOND, + GST_SEEK_TYPE_SET, 380 * GST_MSECOND)); + + n_buffers = 0; + + fail_unless_equals_int (gst_element_set_state (pipeline, GST_STATE_PLAYING), + GST_STATE_CHANGE_SUCCESS); + + g_main_loop_run (loop); + + fail_unless_equals_int (n_buffers, 5); + + gst_element_set_state (pipeline, GST_STATE_NULL); + + gst_object_unref (pipeline); + g_main_loop_unref (loop); + gst_caps_unref (caps1); + gst_caps_unref (caps2); + g_source_remove (bus_watch); +} + +GST_END_TEST; + +GST_START_TEST (test_imagefreeze_eos) +{ + GstElement *pipeline; + GstElement *src; + GstCaps *caps1, *caps2; + GstBus *bus; + GMainLoop *loop; + GstFormat fmt = GST_FORMAT_TIME; + gint64 position; + guint bus_watch = 0; + GstVideoInfo i1, i2; + + gst_video_info_init (&i1); + gst_video_info_set_format (&i1, GST_VIDEO_FORMAT_xRGB, 640, 480); + i1.fps_n = 25; + i1.fps_d = 1; + caps1 = gst_video_info_to_caps (&i1); + + gst_video_info_init (&i2); + gst_video_info_set_format (&i2, GST_VIDEO_FORMAT_xRGB, 640, 480); + i2.fps_n = 25; + i2.fps_d = 1; + caps2 = gst_video_info_to_caps (&i2); + + pipeline = setup_imagefreeze (caps1, caps2, NULL, NULL); + + src = gst_bin_get_by_name (GST_BIN (pipeline), "src"); + fail_unless (src != NULL); + g_object_set (src, "num-buffers", 100, NULL); + + loop = g_main_loop_new (NULL, TRUE); + fail_unless (loop != NULL); + + bus = gst_element_get_bus (pipeline); + fail_unless (bus != NULL); + bus_watch = gst_bus_add_watch (bus, bus_handler, loop); + gst_object_unref (bus); + + fail_unless_equals_int (gst_element_set_state (pipeline, GST_STATE_PAUSED), + GST_STATE_CHANGE_SUCCESS); + + fail_unless (gst_element_seek (pipeline, 1.0, GST_FORMAT_TIME, + GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_SET, + 400 * GST_MSECOND)); + + fail_unless_equals_int (gst_element_set_state (pipeline, GST_STATE_PLAYING), + GST_STATE_CHANGE_SUCCESS); + + g_main_loop_run (loop); + + fail_unless (gst_element_query_position (src, fmt, &position)); + fail_unless_equals_uint64 (position, 40 * GST_MSECOND); + + gst_element_set_state (pipeline, GST_STATE_NULL); + + gst_object_unref (src); + gst_object_unref (pipeline); + g_main_loop_unref (loop); + gst_caps_unref (caps1); + gst_caps_unref (caps2); + g_source_remove (bus_watch); +} + +GST_END_TEST; + +static Suite * +imagefreeze_suite (void) +{ + Suite *s = suite_create ("imagefreeze"); + TCase *tc_chain = tcase_create ("linear"); + + /* time out after 120s, not the default 3 */ + tcase_set_timeout (tc_chain, 120); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_imagefreeze_0_1); + tcase_add_test (tc_chain, test_imagefreeze_25_1_0ms_400ms); + tcase_add_test (tc_chain, test_imagefreeze_25_1_200ms_400ms); + tcase_add_test (tc_chain, test_imagefreeze_25_1_400ms_0ms); + tcase_add_test (tc_chain, test_imagefreeze_25_1_220ms_380ms); + + tcase_add_test (tc_chain, test_imagefreeze_eos); + + return s; +} + +GST_CHECK_MAIN (imagefreeze); diff --git a/tests/check/elements/interleave.c b/tests/check/elements/interleave.c new file mode 100755 index 0000000..67355e8 --- /dev/null +++ b/tests/check/elements/interleave.c @@ -0,0 +1,824 @@ +/* GStreamer unit tests for the interleave element + * Copyright (C) 2007 Tim-Philipp Müller <tim centricular net> + * Copyright (C) 2008 Sebastian Dröge <slomo@circular-chaos.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 0.11: suppress warnings for deprecated API such as GValueArray + * with newer GLib versions (>= 2.31.0) */ +#define GLIB_DISABLE_DEPRECATION_WARNINGS + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef HAVE_VALGRIND +# include <valgrind/valgrind.h> +#endif + +#include <gst/check/gstcheck.h> +#include <gst/audio/audio.h> +#include <gst/audio/audio-enumtypes.h> + +static void +gst_check_setup_events_interleave (GstPad * srcpad, GstElement * element, + GstCaps * caps, GstFormat format, const gchar * stream_id) +{ + GstSegment segment; + + gst_segment_init (&segment, format); + + fail_unless (gst_pad_push_event (srcpad, + gst_event_new_stream_start (stream_id))); + if (caps) + fail_unless (gst_pad_push_event (srcpad, gst_event_new_caps (caps))); + fail_unless (gst_pad_push_event (srcpad, gst_event_new_segment (&segment))); +} + +GST_START_TEST (test_create_and_unref) +{ + GstElement *interleave; + + interleave = gst_element_factory_make ("interleave", NULL); + fail_unless (interleave != NULL); + + gst_element_set_state (interleave, GST_STATE_NULL); + gst_object_unref (interleave); +} + +GST_END_TEST; + +GST_START_TEST (test_request_pads) +{ + GstElement *interleave; + GstPad *pad1, *pad2; + + interleave = gst_element_factory_make ("interleave", NULL); + fail_unless (interleave != NULL); + + pad1 = gst_element_get_request_pad (interleave, "sink_%u"); + fail_unless (pad1 != NULL); + fail_unless_equals_string (GST_OBJECT_NAME (pad1), "sink_0"); + + pad2 = gst_element_get_request_pad (interleave, "sink_%u"); + fail_unless (pad2 != NULL); + fail_unless_equals_string (GST_OBJECT_NAME (pad2), "sink_1"); + + gst_element_release_request_pad (interleave, pad2); + gst_object_unref (pad2); + gst_element_release_request_pad (interleave, pad1); + gst_object_unref (pad1); + + gst_element_set_state (interleave, GST_STATE_NULL); + gst_object_unref (interleave); +} + +GST_END_TEST; + +static GstPad **mysrcpads, *mysinkpad; +static GstBus *bus; +static GstElement *interleave; +static gint have_data; +static gfloat input[2]; + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "format = (string) " GST_AUDIO_NE (F32) ", " + "channels = (int) 2, layout = (string) {interleaved, non-interleaved}, rate = (int) 48000")); + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "format = (string) " GST_AUDIO_NE (F32) ", " + "channels = (int) 1, layout = (string) interleaved, rate = (int) 48000")); + +#define CAPS_48khz \ + "audio/x-raw, " \ + "format = (string) " GST_AUDIO_NE (F32) ", " \ + "channels = (int) 1, layout = (string) non-interleaved," \ + "rate = (int) 48000" + +static GstFlowReturn +interleave_chain_func (GstPad * pad, GstObject * parent, GstBuffer * buffer) +{ + GstMapInfo map; + gfloat *outdata; + gint i; + + fail_unless (GST_IS_BUFFER (buffer)); + gst_buffer_map (buffer, &map, GST_MAP_READ); + outdata = (gfloat *) map.data; + fail_unless_equals_int (map.size, 48000 * 2 * sizeof (gfloat)); + fail_unless (outdata != NULL); + +#ifdef HAVE_VALGRIND + if (!(RUNNING_ON_VALGRIND)) +#endif + for (i = 0; i < 48000 * 2; i += 2) { + fail_unless_equals_float (outdata[i], input[0]); + fail_unless_equals_float (outdata[i + 1], input[1]); + } + gst_buffer_unmap (buffer, &map); + gst_buffer_unref (buffer); + + have_data++; + + return GST_FLOW_OK; +} + +GST_START_TEST (test_interleave_2ch) +{ + GstElement *queue; + GstPad *sink0, *sink1, *src, *tmp; + GstCaps *caps; + gint i; + GstBuffer *inbuf; + gfloat *indata; + GstMapInfo map; + + mysrcpads = g_new0 (GstPad *, 2); + + have_data = 0; + + interleave = gst_element_factory_make ("interleave", NULL); + fail_unless (interleave != NULL); + + queue = gst_element_factory_make ("queue", "queue"); + fail_unless (queue != NULL); + + sink0 = gst_element_get_request_pad (interleave, "sink_%u"); + fail_unless (sink0 != NULL); + fail_unless_equals_string (GST_OBJECT_NAME (sink0), "sink_0"); + + sink1 = gst_element_get_request_pad (interleave, "sink_%u"); + fail_unless (sink1 != NULL); + fail_unless_equals_string (GST_OBJECT_NAME (sink1), "sink_1"); + + mysrcpads[0] = gst_pad_new_from_static_template (&srctemplate, "src0"); + fail_unless (mysrcpads[0] != NULL); + + caps = gst_caps_from_string (CAPS_48khz); + gst_pad_set_active (mysrcpads[0], TRUE); + gst_check_setup_events_interleave (mysrcpads[0], interleave, caps, + GST_FORMAT_TIME, "0"); + gst_pad_use_fixed_caps (mysrcpads[0]); + + mysrcpads[1] = gst_pad_new_from_static_template (&srctemplate, "src1"); + fail_unless (mysrcpads[1] != NULL); + + gst_pad_set_active (mysrcpads[1], TRUE); + gst_check_setup_events_interleave (mysrcpads[1], interleave, caps, + GST_FORMAT_TIME, "1"); + gst_pad_use_fixed_caps (mysrcpads[1]); + + tmp = gst_element_get_static_pad (queue, "sink"); + fail_unless (gst_pad_link (mysrcpads[0], tmp) == GST_PAD_LINK_OK); + gst_object_unref (tmp); + tmp = gst_element_get_static_pad (queue, "src"); + fail_unless (gst_pad_link (tmp, sink0) == GST_PAD_LINK_OK); + gst_object_unref (tmp); + + fail_unless (gst_pad_link (mysrcpads[1], sink1) == GST_PAD_LINK_OK); + + mysinkpad = gst_pad_new_from_static_template (&sinktemplate, "sink"); + fail_unless (mysinkpad != NULL); + gst_pad_set_chain_function (mysinkpad, interleave_chain_func); + gst_pad_set_active (mysinkpad, TRUE); + + src = gst_element_get_static_pad (interleave, "src"); + fail_unless (src != NULL); + fail_unless (gst_pad_link (src, mysinkpad) == GST_PAD_LINK_OK); + gst_object_unref (src); + + bus = gst_bus_new (); + gst_element_set_bus (interleave, bus); + + fail_unless (gst_element_set_state (interleave, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS); + fail_unless (gst_element_set_state (queue, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS); + + input[0] = -1.0; + inbuf = gst_buffer_new_and_alloc (48000 * sizeof (gfloat)); + gst_buffer_map (inbuf, &map, GST_MAP_WRITE); + indata = (gfloat *) map.data; + for (i = 0; i < 48000; i++) + indata[i] = -1.0; + gst_buffer_unmap (inbuf, &map); + fail_unless (gst_pad_push (mysrcpads[0], inbuf) == GST_FLOW_OK); + + input[1] = 1.0; + inbuf = gst_buffer_new_and_alloc (48000 * sizeof (gfloat)); + gst_buffer_map (inbuf, &map, GST_MAP_WRITE); + indata = (gfloat *) map.data; + for (i = 0; i < 48000; i++) + indata[i] = 1.0; + gst_buffer_unmap (inbuf, &map); + fail_unless (gst_pad_push (mysrcpads[1], inbuf) == GST_FLOW_OK); + + inbuf = gst_buffer_new_and_alloc (48000 * sizeof (gfloat)); + gst_buffer_map (inbuf, &map, GST_MAP_WRITE); + indata = (gfloat *) map.data; + for (i = 0; i < 48000; i++) + indata[i] = -1.0; + gst_buffer_unmap (inbuf, &map); + fail_unless (gst_pad_push (mysrcpads[0], inbuf) == GST_FLOW_OK); + + inbuf = gst_buffer_new_and_alloc (48000 * sizeof (gfloat)); + gst_buffer_map (inbuf, &map, GST_MAP_WRITE); + indata = (gfloat *) map.data; + for (i = 0; i < 48000; i++) + indata[i] = 1.0; + gst_buffer_unmap (inbuf, &map); + fail_unless (gst_pad_push (mysrcpads[1], inbuf) == GST_FLOW_OK); + + fail_unless (have_data == 2); + + gst_element_set_state (interleave, GST_STATE_NULL); + gst_element_set_state (queue, GST_STATE_NULL); + + gst_object_unref (mysrcpads[0]); + gst_object_unref (mysrcpads[1]); + gst_object_unref (mysinkpad); + + gst_element_release_request_pad (interleave, sink0); + gst_object_unref (sink0); + gst_element_release_request_pad (interleave, sink1); + gst_object_unref (sink1); + + gst_object_unref (interleave); + gst_object_unref (queue); + gst_object_unref (bus); + gst_caps_unref (caps); + + g_free (mysrcpads); +} + +GST_END_TEST; + +GST_START_TEST (test_interleave_2ch_1eos) +{ + GstElement *queue; + GstPad *sink0, *sink1, *src, *tmp; + GstCaps *caps; + gint i; + GstBuffer *inbuf; + gfloat *indata; + GstMapInfo map; + + mysrcpads = g_new0 (GstPad *, 2); + + have_data = 0; + + interleave = gst_element_factory_make ("interleave", NULL); + fail_unless (interleave != NULL); + + queue = gst_element_factory_make ("queue", "queue"); + fail_unless (queue != NULL); + + sink0 = gst_element_get_request_pad (interleave, "sink_%u"); + fail_unless (sink0 != NULL); + fail_unless_equals_string (GST_OBJECT_NAME (sink0), "sink_0"); + + sink1 = gst_element_get_request_pad (interleave, "sink_%u"); + fail_unless (sink1 != NULL); + fail_unless_equals_string (GST_OBJECT_NAME (sink1), "sink_1"); + + mysrcpads[0] = gst_pad_new_from_static_template (&srctemplate, "src0"); + fail_unless (mysrcpads[0] != NULL); + + caps = gst_caps_from_string (CAPS_48khz); + gst_pad_set_active (mysrcpads[0], TRUE); + gst_check_setup_events_interleave (mysrcpads[0], interleave, caps, + GST_FORMAT_TIME, "0"); + gst_pad_use_fixed_caps (mysrcpads[0]); + + mysrcpads[1] = gst_pad_new_from_static_template (&srctemplate, "src1"); + fail_unless (mysrcpads[1] != NULL); + + gst_pad_set_active (mysrcpads[1], TRUE); + gst_check_setup_events_interleave (mysrcpads[1], interleave, caps, + GST_FORMAT_TIME, "1"); + gst_pad_use_fixed_caps (mysrcpads[1]); + + tmp = gst_element_get_static_pad (queue, "sink"); + fail_unless (gst_pad_link (mysrcpads[0], tmp) == GST_PAD_LINK_OK); + gst_object_unref (tmp); + tmp = gst_element_get_static_pad (queue, "src"); + fail_unless (gst_pad_link (tmp, sink0) == GST_PAD_LINK_OK); + gst_object_unref (tmp); + + fail_unless (gst_pad_link (mysrcpads[1], sink1) == GST_PAD_LINK_OK); + + mysinkpad = gst_pad_new_from_static_template (&sinktemplate, "sink"); + fail_unless (mysinkpad != NULL); + gst_pad_set_chain_function (mysinkpad, interleave_chain_func); + gst_pad_set_active (mysinkpad, TRUE); + + src = gst_element_get_static_pad (interleave, "src"); + fail_unless (src != NULL); + fail_unless (gst_pad_link (src, mysinkpad) == GST_PAD_LINK_OK); + gst_object_unref (src); + + bus = gst_bus_new (); + gst_element_set_bus (interleave, bus); + + fail_unless (gst_element_set_state (interleave, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS); + fail_unless (gst_element_set_state (queue, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS); + + input[0] = -1.0; + inbuf = gst_buffer_new_and_alloc (48000 * sizeof (gfloat)); + gst_buffer_map (inbuf, &map, GST_MAP_WRITE); + indata = (gfloat *) map.data; + for (i = 0; i < 48000; i++) + indata[i] = -1.0; + gst_buffer_unmap (inbuf, &map); + fail_unless (gst_pad_push (mysrcpads[0], inbuf) == GST_FLOW_OK); + + input[1] = 1.0; + inbuf = gst_buffer_new_and_alloc (48000 * sizeof (gfloat)); + gst_buffer_map (inbuf, &map, GST_MAP_WRITE); + indata = (gfloat *) map.data; + for (i = 0; i < 48000; i++) + indata[i] = 1.0; + gst_buffer_unmap (inbuf, &map); + fail_unless (gst_pad_push (mysrcpads[1], inbuf) == GST_FLOW_OK); + + input[0] = 0.0; + gst_pad_push_event (mysrcpads[0], gst_event_new_eos ()); + + input[1] = 1.0; + inbuf = gst_buffer_new_and_alloc (48000 * sizeof (gfloat)); + gst_buffer_map (inbuf, &map, GST_MAP_WRITE); + indata = (gfloat *) map.data; + for (i = 0; i < 48000; i++) + indata[i] = 1.0; + gst_buffer_unmap (inbuf, &map); + fail_unless (gst_pad_push (mysrcpads[1], inbuf) == GST_FLOW_OK); + + fail_unless (have_data == 2); + + gst_element_set_state (interleave, GST_STATE_NULL); + gst_element_set_state (queue, GST_STATE_NULL); + + gst_object_unref (mysrcpads[0]); + gst_object_unref (mysrcpads[1]); + gst_object_unref (mysinkpad); + + gst_element_release_request_pad (interleave, sink0); + gst_object_unref (sink0); + gst_element_release_request_pad (interleave, sink1); + gst_object_unref (sink1); + + gst_object_unref (interleave); + gst_object_unref (queue); + gst_object_unref (bus); + gst_caps_unref (caps); + + g_free (mysrcpads); +} + +GST_END_TEST; + +static void +src_handoff_float32 (GstElement * element, GstBuffer * buffer, GstPad * pad, + gboolean interleaved, gpointer user_data) +{ + gint n = GPOINTER_TO_INT (user_data); + gfloat *data; + gint i; + gsize size; + GstCaps *caps; + guint64 mask; + GstAudioChannelPosition pos; + + switch (n) { + case 0: + case 1: + case 2: + pos = GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT; + break; + case 3: + pos = GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT; + break; + default: + pos = GST_AUDIO_CHANNEL_POSITION_INVALID; + break; + } + + mask = G_GUINT64_CONSTANT (1) << pos; + + caps = gst_caps_new_simple ("audio/x-raw", + "format", G_TYPE_STRING, GST_AUDIO_NE (F32), + "channels", G_TYPE_INT, 1, + "layout", G_TYPE_STRING, interleaved ? "interleaved" : "non-interleaved", + "channel-mask", GST_TYPE_BITMASK, mask, "rate", G_TYPE_INT, 48000, NULL); + + gst_pad_set_caps (pad, caps); + gst_caps_unref (caps); + + size = 48000 * sizeof (gfloat); + data = g_malloc (size); + for (i = 0; i < 48000; i++) + data[i] = (n % 2 == 0) ? -1.0 : 1.0; + + gst_buffer_append_memory (buffer, gst_memory_new_wrapped (0, data, + size, 0, size, data, g_free)); + + GST_BUFFER_OFFSET (buffer) = GST_BUFFER_OFFSET_NONE; + GST_BUFFER_TIMESTAMP (buffer) = GST_CLOCK_TIME_NONE; + GST_BUFFER_OFFSET_END (buffer) = GST_BUFFER_OFFSET_NONE; + GST_BUFFER_DURATION (buffer) = GST_SECOND; +} + +static void +src_handoff_float32_interleaved (GstElement * element, GstBuffer * buffer, + GstPad * pad, gpointer user_data) +{ + src_handoff_float32 (element, buffer, pad, TRUE, user_data); +} + +static void +src_handoff_float32_non_interleaved (GstElement * element, GstBuffer * buffer, + GstPad * pad, gpointer user_data) +{ + src_handoff_float32 (element, buffer, pad, FALSE, user_data); +} + +static void +sink_handoff_float32 (GstElement * element, GstBuffer * buffer, GstPad * pad, + gpointer user_data) +{ + gint i; + GstMapInfo map; + gfloat *data; + GstCaps *caps, *ccaps; + gint n = GPOINTER_TO_INT (user_data); + guint64 mask; + + fail_unless (GST_IS_BUFFER (buffer)); + gst_buffer_map (buffer, &map, GST_MAP_READ); + data = (gfloat *) map.data; + + fail_unless_equals_int (map.size, 48000 * 2 * sizeof (gfloat)); + fail_unless_equals_int (GST_BUFFER_DURATION (buffer), GST_SECOND); + + if (n == 0) { + GstAudioChannelPosition pos[2] = + { GST_AUDIO_CHANNEL_POSITION_NONE, GST_AUDIO_CHANNEL_POSITION_NONE }; + gst_audio_channel_positions_to_mask (pos, 2, FALSE, &mask); + } else if (n == 1) { + GstAudioChannelPosition pos[2] = { GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT, + GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT + }; + gst_audio_channel_positions_to_mask (pos, 2, FALSE, &mask); + } else if (n == 2) { + GstAudioChannelPosition pos[2] = { GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER, + GST_AUDIO_CHANNEL_POSITION_REAR_CENTER + }; + gst_audio_channel_positions_to_mask (pos, 2, FALSE, &mask); + } else { + g_assert_not_reached (); + } + + caps = gst_caps_new_simple ("audio/x-raw", + "format", G_TYPE_STRING, GST_AUDIO_NE (F32), + "channels", G_TYPE_INT, 2, "rate", G_TYPE_INT, 48000, + "layout", G_TYPE_STRING, "interleaved", + "channel-mask", GST_TYPE_BITMASK, mask, NULL); + + ccaps = gst_pad_get_current_caps (pad); + fail_unless (gst_caps_is_equal (caps, ccaps)); + gst_caps_unref (ccaps); + gst_caps_unref (caps); + +#ifdef HAVE_VALGRIND + if (!(RUNNING_ON_VALGRIND)) +#endif + for (i = 0; i < 48000 * 2; i += 2) { + fail_unless_equals_float (data[i], -1.0); + fail_unless_equals_float (data[i + 1], 1.0); + } + gst_buffer_unmap (buffer, &map); + + have_data++; +} + +static void +test_interleave_2ch_pipeline (gboolean interleaved) +{ + GstElement *pipeline, *queue, *src1, *src2, *interleave, *sink; + GstPad *sinkpad0, *sinkpad1, *tmp, *tmp2; + GstMessage *msg; + void *src_handoff_float32 = + interleaved ? &src_handoff_float32_interleaved : + &src_handoff_float32_non_interleaved; + + have_data = 0; + + pipeline = (GstElement *) gst_pipeline_new ("pipeline"); + fail_unless (pipeline != NULL); + + src1 = gst_element_factory_make ("fakesrc", "src1"); + fail_unless (src1 != NULL); + g_object_set (src1, "num-buffers", 4, NULL); + g_object_set (src1, "signal-handoffs", TRUE, NULL); + g_signal_connect (src1, "handoff", G_CALLBACK (src_handoff_float32), + GINT_TO_POINTER (0)); + gst_bin_add (GST_BIN (pipeline), src1); + + src2 = gst_element_factory_make ("fakesrc", "src2"); + fail_unless (src2 != NULL); + g_object_set (src2, "num-buffers", 4, NULL); + g_object_set (src2, "signal-handoffs", TRUE, NULL); + g_signal_connect (src2, "handoff", G_CALLBACK (src_handoff_float32), + GINT_TO_POINTER (1)); + gst_bin_add (GST_BIN (pipeline), src2); + + queue = gst_element_factory_make ("queue", "queue"); + fail_unless (queue != NULL); + gst_bin_add (GST_BIN (pipeline), queue); + + interleave = gst_element_factory_make ("interleave", "interleave"); + fail_unless (interleave != NULL); + gst_bin_add (GST_BIN (pipeline), gst_object_ref (interleave)); + + sinkpad0 = gst_element_get_request_pad (interleave, "sink_%u"); + fail_unless (sinkpad0 != NULL); + tmp = gst_element_get_static_pad (src1, "src"); + fail_unless (gst_pad_link (tmp, sinkpad0) == GST_PAD_LINK_OK); + gst_object_unref (tmp); + + sinkpad1 = gst_element_get_request_pad (interleave, "sink_%u"); + fail_unless (sinkpad1 != NULL); + tmp = gst_element_get_static_pad (src2, "src"); + tmp2 = gst_element_get_static_pad (queue, "sink"); + fail_unless (gst_pad_link (tmp, tmp2) == GST_PAD_LINK_OK); + gst_object_unref (tmp); + gst_object_unref (tmp2); + tmp = gst_element_get_static_pad (queue, "src"); + fail_unless (gst_pad_link (tmp, sinkpad1) == GST_PAD_LINK_OK); + gst_object_unref (tmp); + + sink = gst_element_factory_make ("fakesink", "sink"); + fail_unless (sink != NULL); + g_object_set (sink, "signal-handoffs", TRUE, NULL); + g_signal_connect (sink, "handoff", G_CALLBACK (sink_handoff_float32), + GINT_TO_POINTER (0)); + gst_bin_add (GST_BIN (pipeline), sink); + tmp = gst_element_get_static_pad (interleave, "src"); + tmp2 = gst_element_get_static_pad (sink, "sink"); + fail_unless (gst_pad_link (tmp, tmp2) == GST_PAD_LINK_OK); + gst_object_unref (tmp); + gst_object_unref (tmp2); + + gst_element_set_state (pipeline, GST_STATE_PLAYING); + + msg = gst_bus_poll (GST_ELEMENT_BUS (pipeline), GST_MESSAGE_EOS, -1); + gst_message_unref (msg); + + fail_unless (have_data == 4); + + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_element_release_request_pad (interleave, sinkpad0); + gst_object_unref (sinkpad0); + gst_element_release_request_pad (interleave, sinkpad1); + gst_object_unref (sinkpad1); + gst_object_unref (interleave); + gst_object_unref (pipeline); +} + +GST_START_TEST (test_interleave_2ch_pipeline_interleaved) +{ + test_interleave_2ch_pipeline (TRUE); +} + +GST_END_TEST; + +GST_START_TEST (test_interleave_2ch_pipeline_non_interleaved) +{ + test_interleave_2ch_pipeline (FALSE); +} + +GST_END_TEST; + +GST_START_TEST (test_interleave_2ch_pipeline_input_chanpos) +{ + GstElement *pipeline, *queue, *src1, *src2, *interleave, *sink; + GstPad *sinkpad0, *sinkpad1, *tmp, *tmp2; + GstMessage *msg; + + have_data = 0; + + pipeline = (GstElement *) gst_pipeline_new ("pipeline"); + fail_unless (pipeline != NULL); + + src1 = gst_element_factory_make ("fakesrc", "src1"); + fail_unless (src1 != NULL); + g_object_set (src1, "num-buffers", 4, NULL); + g_object_set (src1, "signal-handoffs", TRUE, NULL); + g_signal_connect (src1, "handoff", + G_CALLBACK (src_handoff_float32_interleaved), GINT_TO_POINTER (2)); + gst_bin_add (GST_BIN (pipeline), src1); + + src2 = gst_element_factory_make ("fakesrc", "src2"); + fail_unless (src2 != NULL); + g_object_set (src2, "num-buffers", 4, NULL); + g_object_set (src2, "signal-handoffs", TRUE, NULL); + g_signal_connect (src2, "handoff", + G_CALLBACK (src_handoff_float32_interleaved), GINT_TO_POINTER (3)); + gst_bin_add (GST_BIN (pipeline), src2); + + queue = gst_element_factory_make ("queue", "queue"); + fail_unless (queue != NULL); + gst_bin_add (GST_BIN (pipeline), queue); + + interleave = gst_element_factory_make ("interleave", "interleave"); + fail_unless (interleave != NULL); + g_object_set (interleave, "channel-positions-from-input", TRUE, NULL); + gst_bin_add (GST_BIN (pipeline), gst_object_ref (interleave)); + + sinkpad0 = gst_element_get_request_pad (interleave, "sink_%u"); + fail_unless (sinkpad0 != NULL); + tmp = gst_element_get_static_pad (src1, "src"); + fail_unless (gst_pad_link (tmp, sinkpad0) == GST_PAD_LINK_OK); + gst_object_unref (tmp); + + sinkpad1 = gst_element_get_request_pad (interleave, "sink_%u"); + fail_unless (sinkpad1 != NULL); + tmp = gst_element_get_static_pad (src2, "src"); + tmp2 = gst_element_get_static_pad (queue, "sink"); + fail_unless (gst_pad_link (tmp, tmp2) == GST_PAD_LINK_OK); + gst_object_unref (tmp); + gst_object_unref (tmp2); + tmp = gst_element_get_static_pad (queue, "src"); + fail_unless (gst_pad_link (tmp, sinkpad1) == GST_PAD_LINK_OK); + gst_object_unref (tmp); + + sink = gst_element_factory_make ("fakesink", "sink"); + fail_unless (sink != NULL); + g_object_set (sink, "signal-handoffs", TRUE, NULL); + g_signal_connect (sink, "handoff", G_CALLBACK (sink_handoff_float32), + GINT_TO_POINTER (1)); + gst_bin_add (GST_BIN (pipeline), sink); + tmp = gst_element_get_static_pad (interleave, "src"); + tmp2 = gst_element_get_static_pad (sink, "sink"); + fail_unless (gst_pad_link (tmp, tmp2) == GST_PAD_LINK_OK); + gst_object_unref (tmp); + gst_object_unref (tmp2); + + gst_element_set_state (pipeline, GST_STATE_PLAYING); + + msg = gst_bus_poll (GST_ELEMENT_BUS (pipeline), GST_MESSAGE_EOS, -1); + gst_message_unref (msg); + + fail_unless (have_data == 4); + + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_element_release_request_pad (interleave, sinkpad0); + gst_object_unref (sinkpad0); + gst_element_release_request_pad (interleave, sinkpad1); + gst_object_unref (sinkpad1); + gst_object_unref (interleave); + gst_object_unref (pipeline); +} + +GST_END_TEST; + +GST_START_TEST (test_interleave_2ch_pipeline_custom_chanpos) +{ + GstElement *pipeline, *queue, *src1, *src2, *interleave, *sink; + GstPad *sinkpad0, *sinkpad1, *tmp, *tmp2; + GstMessage *msg; + GValueArray *arr; + GValue val = { 0, }; + + have_data = 0; + + pipeline = (GstElement *) gst_pipeline_new ("pipeline"); + fail_unless (pipeline != NULL); + + src1 = gst_element_factory_make ("fakesrc", "src1"); + fail_unless (src1 != NULL); + g_object_set (src1, "num-buffers", 4, NULL); + g_object_set (src1, "signal-handoffs", TRUE, NULL); + g_signal_connect (src1, "handoff", + G_CALLBACK (src_handoff_float32_interleaved), GINT_TO_POINTER (0)); + gst_bin_add (GST_BIN (pipeline), src1); + + src2 = gst_element_factory_make ("fakesrc", "src2"); + fail_unless (src2 != NULL); + g_object_set (src2, "num-buffers", 4, NULL); + g_object_set (src2, "signal-handoffs", TRUE, NULL); + g_signal_connect (src2, "handoff", + G_CALLBACK (src_handoff_float32_interleaved), GINT_TO_POINTER (1)); + gst_bin_add (GST_BIN (pipeline), src2); + + queue = gst_element_factory_make ("queue", "queue"); + fail_unless (queue != NULL); + gst_bin_add (GST_BIN (pipeline), queue); + + interleave = gst_element_factory_make ("interleave", "interleave"); + fail_unless (interleave != NULL); + g_object_set (interleave, "channel-positions-from-input", FALSE, NULL); + arr = g_value_array_new (2); + + g_value_init (&val, GST_TYPE_AUDIO_CHANNEL_POSITION); + g_value_set_enum (&val, GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER); + g_value_array_append (arr, &val); + g_value_reset (&val); + g_value_set_enum (&val, GST_AUDIO_CHANNEL_POSITION_REAR_CENTER); + g_value_array_append (arr, &val); + g_value_unset (&val); + + g_object_set (interleave, "channel-positions", arr, NULL); + g_value_array_free (arr); + gst_bin_add (GST_BIN (pipeline), gst_object_ref (interleave)); + + sinkpad0 = gst_element_get_request_pad (interleave, "sink_%u"); + fail_unless (sinkpad0 != NULL); + tmp = gst_element_get_static_pad (src1, "src"); + fail_unless (gst_pad_link (tmp, sinkpad0) == GST_PAD_LINK_OK); + gst_object_unref (tmp); + + sinkpad1 = gst_element_get_request_pad (interleave, "sink_%u"); + fail_unless (sinkpad1 != NULL); + tmp = gst_element_get_static_pad (src2, "src"); + tmp2 = gst_element_get_static_pad (queue, "sink"); + fail_unless (gst_pad_link (tmp, tmp2) == GST_PAD_LINK_OK); + gst_object_unref (tmp); + gst_object_unref (tmp2); + tmp = gst_element_get_static_pad (queue, "src"); + fail_unless (gst_pad_link (tmp, sinkpad1) == GST_PAD_LINK_OK); + gst_object_unref (tmp); + + sink = gst_element_factory_make ("fakesink", "sink"); + fail_unless (sink != NULL); + g_object_set (sink, "signal-handoffs", TRUE, NULL); + g_signal_connect (sink, "handoff", G_CALLBACK (sink_handoff_float32), + GINT_TO_POINTER (2)); + gst_bin_add (GST_BIN (pipeline), sink); + tmp = gst_element_get_static_pad (interleave, "src"); + tmp2 = gst_element_get_static_pad (sink, "sink"); + fail_unless (gst_pad_link (tmp, tmp2) == GST_PAD_LINK_OK); + gst_object_unref (tmp); + gst_object_unref (tmp2); + + gst_element_set_state (pipeline, GST_STATE_PLAYING); + + msg = gst_bus_poll (GST_ELEMENT_BUS (pipeline), GST_MESSAGE_EOS, -1); + gst_message_unref (msg); + + fail_unless (have_data == 4); + + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_element_release_request_pad (interleave, sinkpad0); + gst_object_unref (sinkpad0); + gst_element_release_request_pad (interleave, sinkpad1); + gst_object_unref (sinkpad1); + gst_object_unref (interleave); + gst_object_unref (pipeline); +} + +GST_END_TEST; + +static Suite * +interleave_suite (void) +{ + Suite *s = suite_create ("interleave"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_set_timeout (tc_chain, 180); + tcase_add_test (tc_chain, test_create_and_unref); + tcase_add_test (tc_chain, test_request_pads); + tcase_add_test (tc_chain, test_interleave_2ch); + tcase_add_test (tc_chain, test_interleave_2ch_1eos); + tcase_add_test (tc_chain, test_interleave_2ch_pipeline_interleaved); + tcase_add_test (tc_chain, test_interleave_2ch_pipeline_non_interleaved); + tcase_add_test (tc_chain, test_interleave_2ch_pipeline_input_chanpos); + tcase_add_test (tc_chain, test_interleave_2ch_pipeline_custom_chanpos); + + return s; +} + +GST_CHECK_MAIN (interleave); diff --git a/tests/check/elements/jpegdec.c b/tests/check/elements/jpegdec.c new file mode 100644 index 0000000..81b8b91 --- /dev/null +++ b/tests/check/elements/jpegdec.c @@ -0,0 +1,156 @@ +/* GStreamer + * unit test for jpegdec + * Copyright (C) <2010> Thiago Santos <thiago.sousa.santos@collabora.co.uk> + * Copyright (C) <2012> Mathias Hasselmann <mathias@openismus.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. + */ + +#include <unistd.h> + +#include <gio/gio.h> +#include <gst/check/gstcheck.h> +#include <gst/app/gstappsink.h> +#include <gst/pbutils/gstdiscoverer.h> + +/* Verify jpegdec is working when explictly requested by a pipeline. */ +GST_START_TEST (test_jpegdec_explicit) +{ + GstElement *pipeline, *source, *dec, *sink; + GstSample *sample; + + /* construct a pipeline that explicitly uses jpegdec */ + pipeline = gst_pipeline_new (NULL); + source = gst_element_factory_make ("filesrc", NULL); + dec = gst_element_factory_make ("jpegdec", NULL); + sink = gst_element_factory_make ("appsink", NULL); + + gst_bin_add_many (GST_BIN (pipeline), source, dec, sink, NULL); + gst_element_link_many (source, dec, sink, NULL); + + /* point that pipeline to our test image */ + { + char *filename = g_build_filename (GST_TEST_FILES_PATH, "image.jpg", NULL); + g_object_set (G_OBJECT (source), "location", filename, NULL); + g_free (filename); + } + + gst_element_set_state (pipeline, GST_STATE_PLAYING); + + sample = gst_app_sink_pull_sample (GST_APP_SINK (sink)); + fail_unless (GST_IS_SAMPLE (sample)); + + /* do some basic checks to verify image decoding */ + { + GstCaps *decoded; + GstCaps *expected; + + decoded = gst_sample_get_caps (sample); + expected = gst_caps_from_string ("video/x-raw, width=120, height=160"); + + fail_unless (gst_caps_is_always_compatible (decoded, expected)); + + gst_caps_unref (expected); + } + gst_sample_unref (sample); + + /* wait for EOS */ + sample = gst_app_sink_pull_sample (GST_APP_SINK (sink)); + fail_unless (sample == NULL); + fail_unless (gst_app_sink_is_eos (GST_APP_SINK (sink))); + + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_object_unref (pipeline); +} + +GST_END_TEST; + +/* Verify JPEG discovery is working. Right now jpegdec would be used, + * but I have no idea how to actually verify this. */ +GST_START_TEST (test_jpegdec_discover) +{ + GstDiscoverer *disco; + GError *error = NULL; + char *uri; + GstDiscovererInfo *info; + GstDiscovererVideoInfo *video; + + disco = gst_discoverer_new (5 * GST_SECOND, &error); + + fail_unless (GST_IS_DISCOVERER (disco)); + fail_unless (error == NULL, "%s", (error ? error->message : "")); + + { + GFile *testdir = g_file_new_for_path (GST_TEST_FILES_PATH); + GFile *testfile = g_file_resolve_relative_path (testdir, "image.jpg"); + uri = g_file_get_uri (testfile); + g_object_unref (testfile); + g_object_unref (testdir); + } + + info = gst_discoverer_discover_uri (disco, uri, &error); + fail_unless (GST_IS_DISCOVERER_INFO (info)); + fail_unless (error == NULL, "%s: %s", uri, (error ? error->message : "")); + + fail_unless_equals_string (gst_discoverer_info_get_uri (info), uri); + fail_unless_equals_int (gst_discoverer_info_get_result (info), + GST_DISCOVERER_OK); + + video = + GST_DISCOVERER_VIDEO_INFO (gst_discoverer_info_get_stream_info (info)); + fail_unless (video != NULL); + + fail_unless (gst_discoverer_video_info_is_image (video)); + fail_unless_equals_int (gst_discoverer_video_info_get_width (video), 120); + fail_unless_equals_int (gst_discoverer_video_info_get_height (video), 160); + + gst_discoverer_info_unref (video); + gst_discoverer_info_unref (info); + g_free (uri); + g_object_unref (disco); +} + +GST_END_TEST; + +static Suite * +jpegdec_suite (void) +{ + Suite *s = suite_create ("jpegdec"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_jpegdec_explicit); + tcase_add_test (tc_chain, test_jpegdec_discover); + + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = jpegdec_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/jpegenc.c b/tests/check/elements/jpegenc.c new file mode 100644 index 0000000..fe55fb2 --- /dev/null +++ b/tests/check/elements/jpegenc.c @@ -0,0 +1,254 @@ +/* GStreamer + * + * unit test for jpegenc + * + * Copyright (C) <2010> Thiago Santos <thiago.sousa.santos@collabora.co.uk> + * + * 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 <unistd.h> + +#include <gst/check/gstcheck.h> +#include <gst/app/gstappsink.h> + +/* For ease of programming we use globals to keep refs for our floating + * sink pads we create; otherwise we always have to do get_pad, + * get_peer, and then remove references in every test function */ +static GstPad *mysinkpad; +static GstPad *mysrcpad; + +#define JPEG_CAPS_STRING "image/jpeg" + +#define JPEG_CAPS_RESTRICTIVE "image/jpeg, " \ + "width = (int) [100, 200], " \ + "framerate = (fraction) 25/1, " \ + "extraparameter = (string) { abc, def }" + +static GstStaticPadTemplate jpeg_sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (JPEG_CAPS_STRING)); + +static GstStaticPadTemplate any_sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate jpeg_restrictive_sinktemplate = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (JPEG_CAPS_RESTRICTIVE)); + +static GstStaticPadTemplate any_srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + + +static GstElement * +setup_jpegenc (GstStaticPadTemplate * sinktemplate) +{ + GstElement *jpegenc; + + GST_DEBUG ("setup_jpegenc"); + jpegenc = gst_check_setup_element ("jpegenc"); + mysinkpad = gst_check_setup_sink_pad (jpegenc, sinktemplate); + mysrcpad = gst_check_setup_src_pad (jpegenc, &any_srctemplate); + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + + return jpegenc; +} + +static void +cleanup_jpegenc (GstElement * jpegenc) +{ + GST_DEBUG ("cleanup_jpegenc"); + gst_element_set_state (jpegenc, GST_STATE_NULL); + + gst_pad_set_active (mysrcpad, FALSE); + gst_pad_set_active (mysinkpad, FALSE); + gst_check_teardown_sink_pad (jpegenc); + gst_check_teardown_src_pad (jpegenc); + gst_check_teardown_element (jpegenc); +} + +static GstBuffer * +create_video_buffer (GstCaps * caps) +{ + GstElement *pipeline; + GstElement *cf; + GstElement *sink; + GstSample *sample; + GstBuffer *buffer; + + pipeline = + gst_parse_launch + ("videotestsrc num-buffers=1 ! capsfilter name=cf ! appsink name=sink", + NULL); + g_assert (pipeline != NULL); + + cf = gst_bin_get_by_name (GST_BIN (pipeline), "cf"); + sink = gst_bin_get_by_name (GST_BIN (pipeline), "sink"); + + g_object_set (G_OBJECT (cf), "caps", caps, NULL); + + gst_element_set_state (pipeline, GST_STATE_PLAYING); + + sample = gst_app_sink_pull_sample (GST_APP_SINK (sink)); + + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_object_unref (pipeline); + gst_object_unref (sink); + gst_object_unref (cf); + + buffer = gst_sample_get_buffer (sample); + gst_buffer_ref (buffer); + + gst_sample_unref (sample); + + return buffer; +} + + +GST_START_TEST (test_jpegenc_getcaps) +{ + GstElement *jpegenc; + GstPad *sinkpad; + GstCaps *caps; + GstStructure *structure; + gint fps_n; + gint fps_d; + const GValue *value; + + /* we are going to do some get caps to confirm it doesn't return non-subset + * caps */ + + jpegenc = setup_jpegenc (&any_sinktemplate); + sinkpad = gst_element_get_static_pad (jpegenc, "sink"); + /* this should assert if non-subset */ + caps = gst_pad_query_caps (sinkpad, NULL); + gst_caps_unref (caps); + gst_object_unref (sinkpad); + cleanup_jpegenc (jpegenc); + + jpegenc = setup_jpegenc (&jpeg_sinktemplate); + sinkpad = gst_element_get_static_pad (jpegenc, "sink"); + /* this should assert if non-subset */ + caps = gst_pad_query_caps (sinkpad, NULL); + gst_caps_unref (caps); + gst_object_unref (sinkpad); + cleanup_jpegenc (jpegenc); + + /* now use a more restricted one and check the resulting caps */ + jpegenc = setup_jpegenc (&jpeg_restrictive_sinktemplate); + sinkpad = gst_element_get_static_pad (jpegenc, "sink"); + /* this should assert if non-subset */ + caps = gst_pad_query_caps (sinkpad, NULL); + structure = gst_caps_get_structure (caps, 0); + + /* check the width */ + value = gst_structure_get_value (structure, "width"); + fail_unless (gst_value_get_int_range_min (value) == 100); + fail_unless (gst_value_get_int_range_max (value) == 200); + + fail_unless (gst_structure_get_fraction (structure, "framerate", &fps_n, + &fps_d)); + fail_unless (fps_n == 25); + fail_unless (fps_d == 1); + + gst_caps_unref (caps); + gst_object_unref (sinkpad); + cleanup_jpegenc (jpegenc); +} + +GST_END_TEST; + + +GST_START_TEST (test_jpegenc_different_caps) +{ + GstElement *jpegenc; + GstBuffer *buffer; + GstCaps *caps; + GstCaps *allowed_caps; + + /* now use a more restricted one and check the resulting caps */ + jpegenc = setup_jpegenc (&any_sinktemplate); + gst_element_set_state (jpegenc, GST_STATE_PLAYING); + + /* push first buffer with 800x600 resolution */ + caps = gst_caps_new_simple ("video/x-raw", "width", G_TYPE_INT, + 800, "height", G_TYPE_INT, 600, "framerate", + GST_TYPE_FRACTION, 1, 1, "format", G_TYPE_STRING, "I420", NULL); + gst_check_setup_events (mysrcpad, jpegenc, caps, GST_FORMAT_TIME); + fail_unless ((buffer = create_video_buffer (caps)) != NULL); + gst_caps_unref (caps); + fail_unless (gst_pad_push (mysrcpad, buffer) == GST_FLOW_OK); + + /* check the allowed caps to see if a second buffer with a different + * caps could be negotiated */ + allowed_caps = gst_pad_get_allowed_caps (mysrcpad); + + /* the caps we want to negotiate to */ + caps = gst_caps_new_simple ("video/x-raw", "width", G_TYPE_INT, + 640, "height", G_TYPE_INT, 480, "framerate", + GST_TYPE_FRACTION, 1, 1, "format", G_TYPE_STRING, "I420", NULL); + fail_unless (gst_caps_can_intersect (allowed_caps, caps)); + fail_unless (gst_pad_set_caps (mysrcpad, caps)); + + /* push second buffer with 640x480 resolution */ + buffer = create_video_buffer (caps); + gst_caps_unref (caps); + fail_unless (gst_pad_push (mysrcpad, buffer) == GST_FLOW_OK); + + gst_caps_unref (allowed_caps); + gst_element_set_state (jpegenc, GST_STATE_NULL); + cleanup_jpegenc (jpegenc); +} + +GST_END_TEST; + +static Suite * +jpegenc_suite (void) +{ + Suite *s = suite_create ("jpegenc"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_jpegenc_getcaps); + tcase_add_test (tc_chain, test_jpegenc_different_caps); + + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = jpegenc_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/level.c b/tests/check/elements/level.c new file mode 100644 index 0000000..73f0340 --- /dev/null +++ b/tests/check/elements/level.c @@ -0,0 +1,604 @@ +/* GStreamer + * + * unit test for level + * + * Copyright (C) <2005> Thomas Vander Stichele <thomas at apestaart 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. + */ + +#include <unistd.h> +#include <math.h> + +/* suppress warnings for deprecated API such as GValueArray + * with newer GLib versions (>= 2.31.0) */ +#define GLIB_DISABLE_DEPRECATION_WARNINGS + +#include <gst/audio/audio.h> +#include <gst/check/gstcheck.h> + +/* 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; + +#define LEVEL_CAPS_TEMPLATE_STRING \ + "audio/x-raw, " \ + "format = (string) { "GST_AUDIO_NE(S16)", "GST_AUDIO_NE(F32)" }, " \ + "layout = (string) interleaved, " \ + "rate = (int) [ 1, MAX ], " \ + "channels = (int) [ 1, 8 ]" + +/* we use rate = 1000 here for easy buffer size calculations */ +#define LEVEL_S16_CAPS_STRING \ + "audio/x-raw, " \ + "format = (string) "GST_AUDIO_NE(S16)", " \ + "layout = (string) interleaved, " \ + "rate = (int) 1000, " \ + "channels = (int) 2, " \ + "channel-mask = (bitmask) 3" + +#define LEVEL_F32_CAPS_STRING \ + "audio/x-raw, " \ + "format = (string) "GST_AUDIO_NE(F32)", " \ + "layout = (string) interleaved, " \ + "rate = (int) 1000, " \ + "channels = (int) 2, " \ + "channel-mask = (bitmask) 3" + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (LEVEL_CAPS_TEMPLATE_STRING) + ); +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (LEVEL_CAPS_TEMPLATE_STRING) + ); + +/* takes over reference for outcaps */ +static GstElement * +setup_level (const gchar * caps_str) +{ + GstElement *level; + GstCaps *caps; + + GST_DEBUG ("setup_level"); + level = gst_check_setup_element ("level"); + mysrcpad = gst_check_setup_src_pad (level, &srctemplate); + mysinkpad = gst_check_setup_sink_pad (level, &sinktemplate); + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + + caps = gst_caps_from_string (caps_str); + gst_check_setup_events (mysrcpad, level, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + + return level; +} + +static void +cleanup_level (GstElement * level) +{ + GST_DEBUG ("cleanup_level"); + + gst_pad_set_active (mysrcpad, FALSE); + gst_pad_set_active (mysinkpad, FALSE); + gst_check_teardown_src_pad (level); + gst_check_teardown_sink_pad (level); + gst_check_teardown_element (level); +} + +/* create a 0.1 sec buffer stereo buffer */ +static GstBuffer * +create_s16_buffer (gint16 val_l, gint16 val_r) +{ + GstBuffer *buf = gst_buffer_new_and_alloc (2 * 100 * sizeof (gint16)); + GstMapInfo map; + gint j; + gint16 *data; + + gst_buffer_map (buf, &map, GST_MAP_WRITE); + data = (gint16 *) map.data; + for (j = 0; j < 100; ++j) { + *(data++) = val_l; + *(data++) = val_r; + } + gst_buffer_unmap (buf, &map); + GST_BUFFER_TIMESTAMP (buf) = G_GUINT64_CONSTANT (0); + return buf; +} + +static GstBuffer * +create_f32_buffer (gfloat val_l, gfloat val_r) +{ + GstBuffer *buf = gst_buffer_new_and_alloc (2 * 100 * sizeof (gfloat)); + GstMapInfo map; + gint j; + gfloat *data; + + gst_buffer_map (buf, &map, GST_MAP_WRITE); + data = (gfloat *) map.data; + for (j = 0; j < 100; ++j) { + *(data++) = val_l; + *(data++) = val_r; + } + gst_buffer_unmap (buf, &map); + GST_BUFFER_TIMESTAMP (buf) = G_GUINT64_CONSTANT (0); + return buf; +} + +/* tests */ + +GST_START_TEST (test_ref_counts) +{ + GstElement *level; + GstBuffer *inbuffer, *outbuffer; + GstBus *bus; + GstMessage *message; + + level = setup_level (LEVEL_S16_CAPS_STRING); + g_object_set (level, "message", TRUE, "interval", GST_SECOND / 10, NULL); + fail_unless (gst_element_set_state (level, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + /* create a bus to get the level message on */ + bus = gst_bus_new (); + ASSERT_OBJECT_REFCOUNT (bus, "bus", 1); + gst_element_set_bus (level, bus); + ASSERT_OBJECT_REFCOUNT (bus, "bus", 2); + + /* create a fake 0.1 sec buffer with a half-amplitude block signal */ + inbuffer = create_s16_buffer (16536, 16536); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being collected on the global buffer list */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + + message = gst_bus_poll (bus, GST_MESSAGE_ELEMENT, -1); + ASSERT_OBJECT_REFCOUNT (message, "message", 1); + + fail_unless (message != NULL); + fail_unless (GST_MESSAGE_SRC (message) == GST_OBJECT (level)); + fail_unless (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ELEMENT); + + /* clean up */ + /* flush current messages,and future state change messages */ + gst_bus_set_flushing (bus, TRUE); + + /* message has a ref to the element */ + ASSERT_OBJECT_REFCOUNT (level, "level", 2); + gst_message_unref (message); + ASSERT_OBJECT_REFCOUNT (level, "level", 1); + + gst_element_set_bus (level, NULL); + ASSERT_OBJECT_REFCOUNT (bus, "bus", 1); + gst_object_unref (bus); + gst_buffer_unref (outbuffer); + fail_unless (gst_element_set_state (level, + GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS, "could not set to null"); + ASSERT_OBJECT_REFCOUNT (level, "level", 1); + cleanup_level (level); +} + +GST_END_TEST; + +GST_START_TEST (test_message_is_valid) +{ + GstElement *level; + GstBuffer *inbuffer; + GstBus *bus; + GstMessage *message; + const GstStructure *structure; + GstClockTime endtime, ts, duration; + + level = setup_level (LEVEL_S16_CAPS_STRING); + g_object_set (level, "message", TRUE, "interval", GST_SECOND / 10, NULL); + gst_element_set_state (level, GST_STATE_PLAYING); + /* create a bus to get the level message on */ + bus = gst_bus_new (); + gst_element_set_bus (level, bus); + + /* create a fake 0.1 sec buffer with a half-amplitude block signal and push */ + inbuffer = create_s16_buffer (16536, 16536); + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + + message = gst_bus_poll (bus, GST_MESSAGE_ELEMENT, -1); + fail_unless (message != NULL); + structure = gst_message_get_structure (message); + fail_if (structure == NULL); + fail_unless_equals_string (gst_structure_get_name (structure), "level"); + fail_unless (gst_structure_get_clock_time (structure, "endtime", &endtime)); + fail_unless (gst_structure_get_clock_time (structure, "timestamp", &ts)); + fail_unless (gst_structure_get_clock_time (structure, "duration", &duration)); + + /* clean up */ + /* flush current messages,and future state change messages */ + gst_bus_set_flushing (bus, TRUE); + gst_message_unref (message); + gst_element_set_bus (level, NULL); + gst_object_unref (bus); + gst_element_set_state (level, GST_STATE_NULL); + cleanup_level (level); +} + +GST_END_TEST; + +GST_START_TEST (test_int16) +{ + GstElement *level; + GstBuffer *inbuffer, *outbuffer; + GstBus *bus; + GstMessage *message; + const GstStructure *structure; + gint i, j; + const GValue *list, *value; + gdouble dB; + const gchar *fields[3] = { "rms", "peak", "decay" }; + + level = setup_level (LEVEL_S16_CAPS_STRING); + g_object_set (level, "message", TRUE, "interval", GST_SECOND / 10, NULL); + gst_element_set_state (level, GST_STATE_PLAYING); + /* create a bus to get the level message on */ + bus = gst_bus_new (); + gst_element_set_bus (level, bus); + + /* create a fake 0.1 sec buffer with a half-amplitude block signal */ + inbuffer = create_s16_buffer (16536, 16536); + + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + + message = gst_bus_poll (bus, GST_MESSAGE_ELEMENT, -1); + structure = gst_message_get_structure (message); + + /* block wave of half amplitude has -5.94 dB for rms, peak and decay */ + for (i = 0; i < 2; ++i) { + for (j = 0; j < 3; ++j) { + GValueArray *arr; + + list = gst_structure_get_value (structure, fields[j]); + arr = g_value_get_boxed (list); + value = g_value_array_get_nth (arr, i); + dB = g_value_get_double (value); + GST_DEBUG ("%s is %lf", fields[j], dB); + fail_if (dB < -6.1); + fail_if (dB > -5.9); + } + } + + /* clean up */ + /* flush current messages,and future state change messages */ + gst_bus_set_flushing (bus, TRUE); + gst_message_unref (message); + gst_element_set_bus (level, NULL); + gst_object_unref (bus); + gst_buffer_unref (outbuffer); + gst_element_set_state (level, GST_STATE_NULL); + cleanup_level (level); +} + +GST_END_TEST; + +GST_START_TEST (test_int16_panned) +{ + GstElement *level; + GstBuffer *inbuffer, *outbuffer; + GstBus *bus; + GstMessage *message; + const GstStructure *structure; + gint j; + const GValue *list, *value; + gdouble dB; + const gchar *fields[3] = { "rms", "peak", "decay" }; + + level = setup_level (LEVEL_S16_CAPS_STRING); + g_object_set (level, "message", TRUE, "interval", GST_SECOND / 10, NULL); + gst_element_set_state (level, GST_STATE_PLAYING); + /* create a bus to get the level message on */ + bus = gst_bus_new (); + gst_element_set_bus (level, bus); + + /* create a fake 0.1 sec buffer with a half-amplitude block signal */ + inbuffer = create_s16_buffer (0, 16536); + + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + + message = gst_bus_poll (bus, GST_MESSAGE_ELEMENT, -1); + structure = gst_message_get_structure (message); + + /* silence has 0 dB for rms, peak and decay */ + for (j = 0; j < 3; ++j) { + GValueArray *arr; + + list = gst_structure_get_value (structure, fields[j]); + arr = g_value_get_boxed (list); + value = g_value_array_get_nth (arr, 0); + dB = g_value_get_double (value); + GST_DEBUG ("%s[0] is %lf", fields[j], dB); +#ifdef HAVE_ISINF + fail_unless (isinf (dB)); +#elif defined (HAVE_FPCLASS) + fail_unless (fpclass (dB) == FP_NINF); +#endif + } + /* block wave of half amplitude has -5.94 dB for rms, peak and decay */ + for (j = 0; j < 3; ++j) { + GValueArray *arr; + + list = gst_structure_get_value (structure, fields[j]); + arr = g_value_get_boxed (list); + value = g_value_array_get_nth (arr, 1); + dB = g_value_get_double (value); + GST_DEBUG ("%s[1] is %lf", fields[j], dB); + fail_if (dB < -6.1); + fail_if (dB > -5.9); + } + + /* clean up */ + /* flush current messages,and future state change messages */ + gst_bus_set_flushing (bus, TRUE); + gst_message_unref (message); + gst_element_set_bus (level, NULL); + gst_object_unref (bus); + gst_buffer_unref (outbuffer); + gst_element_set_state (level, GST_STATE_NULL); + cleanup_level (level); +} + +GST_END_TEST; + +GST_START_TEST (test_float) +{ + GstElement *level; + GstBuffer *inbuffer, *outbuffer; + GstBus *bus; + GstMessage *message; + const GstStructure *structure; + gint i, j; + const GValue *list, *value; + gdouble dB; + const gchar *fields[3] = { "rms", "peak", "decay" }; + + level = setup_level (LEVEL_F32_CAPS_STRING); + g_object_set (level, "message", TRUE, "interval", GST_SECOND / 10, NULL); + gst_element_set_state (level, GST_STATE_PLAYING); + /* create a bus to get the level message on */ + bus = gst_bus_new (); + gst_element_set_bus (level, bus); + + /* create a fake 0.1 sec buffer with a half-amplitude block signal */ + inbuffer = create_f32_buffer (0.5, 0.5); + + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + + message = gst_bus_poll (bus, GST_MESSAGE_ELEMENT, -1); + structure = gst_message_get_structure (message); + + /* block wave of half amplitude has -5.94 dB for rms, peak and decay */ + for (i = 0; i < 2; ++i) { + for (j = 0; j < 3; ++j) { + GValueArray *arr; + + list = gst_structure_get_value (structure, fields[j]); + arr = g_value_get_boxed (list); + value = g_value_array_get_nth (arr, i); + dB = g_value_get_double (value); + GST_DEBUG ("%s is %lf", fields[j], dB); + fail_if (dB < -6.1); + fail_if (dB > -5.9); + } + } + + /* clean up */ + /* flush current messages,and future state change messages */ + gst_bus_set_flushing (bus, TRUE); + gst_message_unref (message); + gst_element_set_bus (level, NULL); + gst_object_unref (bus); + gst_buffer_unref (outbuffer); + gst_element_set_state (level, GST_STATE_NULL); + cleanup_level (level); +} + +GST_END_TEST; + +GST_START_TEST (test_message_on_eos) +{ + GstElement *level; + GstBuffer *inbuffer, *outbuffer; + GstEvent *event; + GstBus *bus; + GstMessage *message; + const GstStructure *structure; + gint i, j; + const GValue *list, *value; + gdouble dB; + + level = setup_level (LEVEL_S16_CAPS_STRING); + g_object_set (level, "message", TRUE, "interval", GST_SECOND / 5, NULL); + gst_element_set_state (level, GST_STATE_PLAYING); + /* create a bus to get the level message on */ + bus = gst_bus_new (); + gst_element_set_bus (level, bus); + + /* create a fake 0.1 sec buffer with a half-amplitude block signal */ + inbuffer = create_s16_buffer (16536, 16536); + + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + + message = gst_bus_poll (bus, GST_MESSAGE_ELEMENT, 0); + + event = gst_event_new_eos (); + fail_unless (gst_pad_push_event (mysrcpad, event) == TRUE); + + message = gst_bus_poll (bus, GST_MESSAGE_ELEMENT, 0); + fail_unless (message != NULL); + structure = gst_message_get_structure (message); + fail_unless_equals_string (gst_structure_get_name (structure), "level"); + + /* block wave of half amplitude has -5.94 dB for rms, peak and decay */ + for (i = 0; i < 2; ++i) { + const gchar *fields[3] = { "rms", "peak", "decay" }; + for (j = 0; j < 3; ++j) { + GValueArray *arr; + + list = gst_structure_get_value (structure, fields[j]); + arr = g_value_get_boxed (list); + value = g_value_array_get_nth (arr, i); + dB = g_value_get_double (value); + GST_DEBUG ("%s is %lf", fields[j], dB); + fail_if (dB < -6.1); + fail_if (dB > -5.9); + } + } + + /* clean up */ + /* flush current messages,and future state change messages */ + gst_bus_set_flushing (bus, TRUE); + gst_message_unref (message); + gst_element_set_bus (level, NULL); + gst_object_unref (bus); + gst_buffer_unref (outbuffer); + gst_element_set_state (level, GST_STATE_NULL); + cleanup_level (level); +} + +GST_END_TEST; + +GST_START_TEST (test_message_count) +{ + GstElement *level; + GstBuffer *inbuffer, *outbuffer; + GstBus *bus; + GstMessage *message; + + level = setup_level (LEVEL_S16_CAPS_STRING); + g_object_set (level, "message", TRUE, "interval", GST_SECOND / 20, NULL); + gst_element_set_state (level, GST_STATE_PLAYING); + /* create a bus to get the level message on */ + bus = gst_bus_new (); + gst_element_set_bus (level, bus); + + /* create a fake 0.1 sec buffer with a half-amplitude block signal */ + inbuffer = create_s16_buffer (16536, 16536); + + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + + /* we should get two messages per buffer */ + message = gst_bus_poll (bus, GST_MESSAGE_ELEMENT, -1); + fail_unless (message != NULL); + gst_message_unref (message); + message = gst_bus_poll (bus, GST_MESSAGE_ELEMENT, -1); + fail_unless (message != NULL); + gst_message_unref (message); + + gst_element_set_bus (level, NULL); + gst_object_unref (bus); + gst_buffer_unref (outbuffer); + gst_element_set_state (level, GST_STATE_NULL); + cleanup_level (level); +} + +GST_END_TEST; + +GST_START_TEST (test_message_timestamps) +{ + GstElement *level; + GstBuffer *inbuffer, *outbuffer; + GstBus *bus; + GstMessage *message; + const GstStructure *structure; + GstClockTime ts1, dur1, ts2; + + level = setup_level (LEVEL_S16_CAPS_STRING); + g_object_set (level, "message", TRUE, "interval", GST_SECOND / 20, NULL); + gst_element_set_state (level, GST_STATE_PLAYING); + /* create a bus to get the level message on */ + bus = gst_bus_new (); + gst_element_set_bus (level, bus); + + /* create a fake 0.1 sec buffer with a half-amplitude block signal */ + inbuffer = create_s16_buffer (16536, 16536); + + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + + /* check that timestamp + duration is contigous to the next timestamp */ + message = gst_bus_poll (bus, GST_MESSAGE_ELEMENT, -1); + structure = gst_message_get_structure (message); + gst_structure_get_clock_time (structure, "timestamp", &ts1); + gst_structure_get_clock_time (structure, "duration", &dur1); + gst_message_unref (message); + + message = gst_bus_poll (bus, GST_MESSAGE_ELEMENT, -1); + structure = gst_message_get_structure (message); + gst_structure_get_clock_time (structure, "timestamp", &ts2); + gst_message_unref (message); + + fail_unless_equals_int64 (ts1 + dur1, ts2); + + gst_element_set_bus (level, NULL); + gst_object_unref (bus); + gst_buffer_unref (outbuffer); + gst_element_set_state (level, GST_STATE_NULL); + cleanup_level (level); +} + +GST_END_TEST; + +static Suite * +level_suite (void) +{ + Suite *s = suite_create ("level"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_ref_counts); + tcase_add_test (tc_chain, test_message_is_valid); + tcase_add_test (tc_chain, test_int16); + tcase_add_test (tc_chain, test_int16_panned); + tcase_add_test (tc_chain, test_float); + tcase_add_test (tc_chain, test_message_on_eos); + tcase_add_test (tc_chain, test_message_count); + tcase_add_test (tc_chain, test_message_timestamps); + + return s; +} + +GST_CHECK_MAIN (level); diff --git a/tests/check/elements/matroskamux.c b/tests/check/elements/matroskamux.c new file mode 100644 index 0000000..1c18e75 --- /dev/null +++ b/tests/check/elements/matroskamux.c @@ -0,0 +1,495 @@ +/* GStreamer + * + * unit test for matroskamux + * + * Copyright (C) <2005> Michal Benes <michal.benes@xeris.cz> + * + * 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 <unistd.h> + +#include <gst/check/gstcheck.h> +#include <gst/base/gstadapter.h> + +/* 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 */ +GstPad *mysrcpad, *mysinkpad; + +#define AC3_CAPS_STRING "audio/x-ac3, " \ + "channels = (int) 1, " \ + "rate = (int) 8000" +#define VORBIS_CAPS_STRING "audio/x-vorbis, " \ + "channels = (int) 1, " \ + "rate = (int) 8000, " \ + "streamheader=(buffer)<10, 2020, 303030>" + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-matroska; audio/x-matroska")); +static GstStaticPadTemplate srcvorbistemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (VORBIS_CAPS_STRING)); + +static GstStaticPadTemplate srcac3template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (AC3_CAPS_STRING)); + + +static GstPad * +setup_src_pad (GstElement * element, GstStaticPadTemplate * template) +{ + GstPad *srcpad, *sinkpad; + + GST_DEBUG_OBJECT (element, "setting up sending pad"); + /* sending pad */ + srcpad = gst_pad_new_from_static_template (template, "src"); + fail_if (srcpad == NULL, "Could not create a srcpad"); + ASSERT_OBJECT_REFCOUNT (srcpad, "srcpad", 1); + gst_pad_set_active (srcpad, TRUE); + + if (!(sinkpad = gst_element_get_static_pad (element, "audio_%u"))) + sinkpad = gst_element_get_request_pad (element, "audio_%u"); + fail_if (sinkpad == NULL, "Could not get sink pad from %s", + GST_ELEMENT_NAME (element)); + /* references are owned by: 1) us, 2) matroskamux, 3) collect pads */ + ASSERT_OBJECT_REFCOUNT (sinkpad, "sinkpad", 3); + fail_unless (gst_pad_link (srcpad, sinkpad) == GST_PAD_LINK_OK, + "Could not link source and %s sink pads", GST_ELEMENT_NAME (element)); + gst_object_unref (sinkpad); /* because we got it higher up */ + + /* references are owned by: 1) matroskamux, 2) collect pads */ + ASSERT_OBJECT_REFCOUNT (sinkpad, "sinkpad", 2); + + return srcpad; +} + +static void +teardown_src_pad (GstElement * element) +{ + GstPad *srcpad, *sinkpad; + + /* clean up floating src pad */ + if (!(sinkpad = gst_element_get_static_pad (element, "audio_0"))) + sinkpad = gst_element_get_request_pad (element, "audio_0"); + /* references are owned by: 1) us, 2) matroskamux, 3) collect pads */ + ASSERT_OBJECT_REFCOUNT (sinkpad, "sinkpad", 3); + srcpad = gst_pad_get_peer (sinkpad); + + gst_pad_unlink (srcpad, sinkpad); + + /* references are owned by: 1) us, 2) matroskamux, 3) collect pads */ + ASSERT_OBJECT_REFCOUNT (sinkpad, "sinkpad", 3); + gst_object_unref (sinkpad); + /* one more ref is held by element itself */ + + /* pad refs held by both creator and this function (through _get_peer) */ + ASSERT_OBJECT_REFCOUNT (srcpad, "srcpad", 2); + gst_object_unref (srcpad); + gst_object_unref (srcpad); +} + +static GstPad * +setup_sink_pad (GstElement * element, GstStaticPadTemplate * template) +{ + GstPad *srcpad, *sinkpad; + + GST_DEBUG_OBJECT (element, "setting up receiving pad"); + /* receiving pad */ + sinkpad = gst_pad_new_from_static_template (template, "sink"); + + fail_if (sinkpad == NULL, "Could not create a sinkpad"); + gst_pad_set_active (sinkpad, TRUE); + + srcpad = gst_element_get_static_pad (element, "src"); + fail_if (srcpad == NULL, "Could not get source pad from %s", + GST_ELEMENT_NAME (element)); + gst_pad_set_chain_function (sinkpad, gst_check_chain_func); + + fail_unless (gst_pad_link (srcpad, sinkpad) == GST_PAD_LINK_OK, + "Could not link %s source and sink pads", GST_ELEMENT_NAME (element)); + gst_object_unref (srcpad); /* because we got it higher up */ + ASSERT_OBJECT_REFCOUNT (srcpad, "srcpad", 2); + + return sinkpad; +} + +static void +teardown_sink_pad (GstElement * element) +{ + GstPad *srcpad, *sinkpad; + + /* clean up floating sink pad */ + srcpad = gst_element_get_static_pad (element, "src"); + sinkpad = gst_pad_get_peer (srcpad); + gst_pad_unlink (srcpad, sinkpad); + + /* pad refs held by both creator and this function (through _get_pad) */ + ASSERT_OBJECT_REFCOUNT (srcpad, "srcpad", 3); + gst_object_unref (srcpad); + /* one more ref is held by element itself */ + + /* pad refs held by both creator and this function (through _get_peer) */ + ASSERT_OBJECT_REFCOUNT (sinkpad, "sinkpad", 2); + gst_object_unref (sinkpad); + gst_object_unref (sinkpad); +} + + +static GstElement * +setup_matroskamux (GstStaticPadTemplate * srctemplate) +{ + GstElement *matroskamux; + + GST_DEBUG ("setup_matroskamux"); + matroskamux = gst_check_setup_element ("matroskamux"); + g_object_set (matroskamux, "version", 1, NULL); + mysrcpad = setup_src_pad (matroskamux, srctemplate); + mysinkpad = setup_sink_pad (matroskamux, &sinktemplate); + + fail_unless (gst_element_set_state (matroskamux, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + return matroskamux; +} + +static void +cleanup_matroskamux (GstElement * matroskamux) +{ + GST_DEBUG ("cleanup_matroskamux"); + gst_element_set_state (matroskamux, GST_STATE_NULL); + + teardown_src_pad (matroskamux); + teardown_sink_pad (matroskamux); + gst_check_teardown_element (matroskamux); +} + +static void +check_buffer_data (GstBuffer * buffer, void *data, size_t data_size) +{ + fail_unless (gst_buffer_get_size (buffer) == data_size); + fail_unless (gst_buffer_memcmp (buffer, 0, data, data_size) == 0); +} + +GST_START_TEST (test_ebml_header) +{ + GstElement *matroskamux; + GstBuffer *inbuffer, *outbuffer; + GstAdapter *adapter; + int num_buffers; + int i; + gint available; + GstCaps *caps; + guint8 data[] = + { 0x1a, 0x45, 0xdf, 0xa3, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, + 0x42, 0x82, 0x89, 0x6d, 0x61, 0x74, 0x72, 0x6f, 0x73, 0x6b, 0x61, 0x00, + 0x42, 0x87, 0x81, 0x01, + 0x42, 0x85, 0x81, 0x01 + }; + + matroskamux = setup_matroskamux (&srcac3template); + + caps = gst_caps_from_string (srcac3template.static_caps.string); + gst_check_setup_events (mysrcpad, matroskamux, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + + inbuffer = gst_buffer_new_allocate (NULL, 1, 0); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + num_buffers = g_list_length (buffers); + fail_unless (num_buffers >= 1, + "expected at least 5 buffers, but got only %d", num_buffers); + + adapter = gst_adapter_new (); + for (i = 0; i < num_buffers; ++i) { + outbuffer = GST_BUFFER (buffers->data); + fail_if (outbuffer == NULL); + buffers = g_list_remove (buffers, outbuffer); + + ASSERT_BUFFER_REFCOUNT (outbuffer, "outbuffer", 1); + + gst_adapter_push (adapter, outbuffer); + } + + available = gst_adapter_available (adapter); + fail_unless (available >= sizeof (data)); + outbuffer = gst_adapter_take_buffer (adapter, sizeof (data)); + g_object_unref (adapter); + + check_buffer_data (outbuffer, data, sizeof (data)); + gst_buffer_unref (outbuffer); + + cleanup_matroskamux (matroskamux); + g_list_free (buffers); + buffers = NULL; +} + +GST_END_TEST; + + +GST_START_TEST (test_vorbis_header) +{ + GstElement *matroskamux; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + int num_buffers; + int i; + gboolean vorbis_header_found = FALSE; + guint8 data[12] = + { 0x63, 0xa2, 0x89, 0x02, 0x01, 0x02, 0x10, 0x20, 0x20, 0x30, 0x30, + 0x30 + }; + + matroskamux = setup_matroskamux (&srcvorbistemplate); + + caps = gst_caps_from_string (VORBIS_CAPS_STRING); + gst_check_setup_events (mysrcpad, matroskamux, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + + inbuffer = gst_buffer_new_allocate (NULL, 1, 0); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + num_buffers = g_list_length (buffers); + + for (i = 0; i < num_buffers; ++i) { + gint j; + gsize buffer_size; + + outbuffer = GST_BUFFER (buffers->data); + fail_if (outbuffer == NULL); + buffer_size = gst_buffer_get_size (outbuffer); + buffers = g_list_remove (buffers, outbuffer); + + if (!vorbis_header_found && buffer_size >= sizeof (data)) { + for (j = 0; j <= buffer_size - sizeof (data); j++) { + if (gst_buffer_memcmp (outbuffer, j, data, sizeof (data)) == 0) { + vorbis_header_found = TRUE; + break; + } + } + } + + ASSERT_BUFFER_REFCOUNT (outbuffer, "outbuffer", 1); + gst_buffer_unref (outbuffer); + outbuffer = NULL; + } + + fail_unless (vorbis_header_found); + + cleanup_matroskamux (matroskamux); + g_list_free (buffers); + buffers = NULL; +} + +GST_END_TEST; + + +GST_START_TEST (test_block_group) +{ + GstElement *matroskamux; + GstBuffer *inbuffer, *outbuffer; + guint8 *indata; + GstCaps *caps; + int num_buffers; + int i; + guint8 data0[] = { 0xa0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, + 0xa1, 0x85, + 0x81, 0x00, 0x01, 0x00 + }; + guint8 data1[] = { 0x42 }; + + matroskamux = setup_matroskamux (&srcac3template); + + caps = gst_caps_from_string (AC3_CAPS_STRING); + gst_check_setup_events (mysrcpad, matroskamux, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + + /* Generate the header */ + inbuffer = gst_buffer_new_allocate (NULL, 1, 0); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + fail_unless_equals_int (gst_pad_push (mysrcpad, inbuffer), GST_FLOW_OK); + num_buffers = g_list_length (buffers); + + for (i = 0; i < num_buffers; ++i) { + outbuffer = GST_BUFFER (buffers->data); + fail_if (outbuffer == NULL); + buffers = g_list_remove (buffers, outbuffer); + + ASSERT_BUFFER_REFCOUNT (outbuffer, "outbuffer", 1); + gst_buffer_unref (outbuffer); + outbuffer = NULL; + } + + g_list_free (buffers); + buffers = NULL; + + /* Now push a buffer */ + indata = g_malloc (1); + inbuffer = gst_buffer_new_wrapped (indata, 1); + indata[0] = 0x42; + GST_BUFFER_TIMESTAMP (inbuffer) = 1000000; + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + num_buffers = g_list_length (buffers); + fail_unless (num_buffers >= 2); + + for (i = 0; i < num_buffers; ++i) { + outbuffer = GST_BUFFER (buffers->data); + fail_if (outbuffer == NULL); + buffers = g_list_remove (buffers, outbuffer); + + switch (i) { + case 0: + check_buffer_data (outbuffer, data0, sizeof (data0)); + break; + case 1: + check_buffer_data (outbuffer, data1, sizeof (data1)); + break; + default: + break; + } + + ASSERT_BUFFER_REFCOUNT (outbuffer, "outbuffer", 1); + gst_buffer_unref (outbuffer); + outbuffer = NULL; + } + + g_list_free (buffers); + buffers = NULL; + + cleanup_matroskamux (matroskamux); +} + +GST_END_TEST; + +GST_START_TEST (test_reset) +{ + GstElement *matroskamux; + GstBuffer *inbuffer; + GstBuffer *outbuffer; + int num_buffers; + int i; + GstCaps *caps; + + matroskamux = setup_matroskamux (&srcac3template); + + caps = gst_caps_from_string (srcac3template.static_caps.string); + gst_check_setup_events (mysrcpad, matroskamux, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + + inbuffer = gst_buffer_new_allocate (NULL, 1, 0); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + num_buffers = g_list_length (buffers); + fail_unless (num_buffers >= 1, + "expected at least 1 buffer, but got only %d", num_buffers); + + fail_unless (gst_element_set_state (matroskamux, + GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS, "could not set to null"); + + fail_unless (gst_element_set_state (matroskamux, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + inbuffer = gst_buffer_new_allocate (NULL, 1, 0); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + num_buffers = g_list_length (buffers); + fail_unless (num_buffers >= 2, + "expected at least 2 buffers, but got only %d", num_buffers); + + for (i = 0; i < num_buffers; ++i) { + outbuffer = GST_BUFFER (buffers->data); + fail_if (outbuffer == NULL); + buffers = g_list_remove (buffers, outbuffer); + + ASSERT_BUFFER_REFCOUNT (outbuffer, "outbuffer", 1); + gst_buffer_unref (outbuffer); + } + + cleanup_matroskamux (matroskamux); + g_list_free (buffers); + buffers = NULL; +} + +GST_END_TEST; + +GST_START_TEST (test_link_webmmux_webm_sink) +{ + static GstStaticPadTemplate webm_sinktemplate = + GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/webm; audio/webm")); + GstElement *mux; + + mux = gst_check_setup_element ("webmmux"); + mysinkpad = setup_sink_pad (mux, &webm_sinktemplate); + fail_unless (mysinkpad != NULL); + + fail_unless (gst_element_set_state (mux, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + gst_element_set_state (mux, GST_STATE_NULL); + + teardown_sink_pad (mux); + gst_check_teardown_element (mux); +} + +GST_END_TEST; + +static Suite * +matroskamux_suite (void) +{ + Suite *s = suite_create ("matroskamux"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_ebml_header); + tcase_add_test (tc_chain, test_vorbis_header); + tcase_add_test (tc_chain, test_block_group); + tcase_add_test (tc_chain, test_reset); + tcase_add_test (tc_chain, test_link_webmmux_webm_sink); + + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = matroskamux_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/matroskaparse.c b/tests/check/elements/matroskaparse.c new file mode 100755 index 0000000..13ef8d8 --- /dev/null +++ b/tests/check/elements/matroskaparse.c @@ -0,0 +1,130 @@ +/* GStreamer unit tests for matroskaparse + * Copyright (C) 2011 Tim-Philipp Müller <tim centricular net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <gst/check/gstcheck.h> + +#include <gst/gst.h> + +static void +run_check_for_file (const gchar * file_name, gboolean push_mode) +{ + GstStateChangeReturn state_ret; + GstMessage *msg; + GstElement *src, *sep, *sink, *matroskaparse, *pipeline; + GstBus *bus; + gchar *path; + + pipeline = gst_pipeline_new ("pipeline"); + fail_unless (pipeline != NULL, "Failed to create pipeline!"); + + bus = gst_element_get_bus (pipeline); + + src = gst_element_factory_make ("filesrc", "filesrc"); + fail_unless (src != NULL, "Failed to create 'filesrc' element!"); + + if (push_mode) { + sep = gst_element_factory_make ("queue", "queue"); + fail_unless (sep != NULL, "Failed to create 'queue' element"); + } else { + sep = gst_element_factory_make ("identity", "identity"); + fail_unless (sep != NULL, "Failed to create 'identity' element"); + } + + matroskaparse = gst_element_factory_make ("matroskaparse", "matroskaparse"); + fail_unless (matroskaparse != NULL, "Failed to create matroskaparse element"); + + sink = gst_element_factory_make ("fakesink", "fakesink"); + fail_unless (sink != NULL, "Failed to create 'fakesink' element!"); + + gst_bin_add_many (GST_BIN (pipeline), src, sep, matroskaparse, sink, NULL); + + fail_unless (gst_element_link (src, sep)); + fail_unless (gst_element_link (sep, matroskaparse)); + fail_unless (gst_element_link (matroskaparse, sink)); + + path = g_build_filename (GST_TEST_FILES_PATH, file_name, NULL); + GST_LOG ("reading file '%s'", path); + g_object_set (src, "location", path, NULL); + + state_ret = gst_element_set_state (pipeline, GST_STATE_PAUSED); + fail_unless (state_ret != GST_STATE_CHANGE_FAILURE); + + if (state_ret == GST_STATE_CHANGE_ASYNC) { + GST_LOG ("waiting for pipeline to reach PAUSED state"); + state_ret = gst_element_get_state (pipeline, NULL, NULL, -1); + fail_unless_equals_int (state_ret, GST_STATE_CHANGE_SUCCESS); + } + + GST_LOG ("PAUSED, let's play a little.."); + state_ret = gst_element_set_state (pipeline, GST_STATE_PLAYING); + fail_unless (state_ret != GST_STATE_CHANGE_FAILURE); + + msg = gst_bus_poll (bus, GST_MESSAGE_EOS | GST_MESSAGE_ERROR, -1); + if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) { + GError *err; + gchar *dbg; + + gst_message_parse_error (msg, &err, &dbg); + gst_object_default_error (GST_MESSAGE_SRC (msg), err, dbg); + g_error ("%s (%s)", err->message, dbg); + g_error_free (err); + g_free (dbg); + } + gst_message_unref (msg); + gst_object_unref (bus); + + fail_unless_equals_int (gst_element_set_state (pipeline, GST_STATE_NULL), + GST_STATE_CHANGE_SUCCESS); + gst_object_unref (pipeline); + + g_free (path); +} + +#if 0 +GST_START_TEST (test_parse_file_pull) +{ + run_check_for_file ("pinknoise-vorbis.mkv", TRUE); +} + +GST_END_TEST; +#endif + +GST_START_TEST (test_parse_file_push) +{ + run_check_for_file ("pinknoise-vorbis.mkv", FALSE); +} + +GST_END_TEST; + +static Suite * +matroskaparse_suite (void) +{ + Suite *s = suite_create ("matroskaparse"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); +#if 0 + tcase_add_test (tc_chain, test_parse_file_pull); +#endif + tcase_add_test (tc_chain, test_parse_file_push); + + return s; +} + +GST_CHECK_MAIN (matroskaparse) diff --git a/tests/check/elements/mpegaudioparse.c b/tests/check/elements/mpegaudioparse.c new file mode 100644 index 0000000..dfe4735 --- /dev/null +++ b/tests/check/elements/mpegaudioparse.c @@ -0,0 +1,170 @@ +/* + * GStreamer + * + * unit test for aacparse + * + * Copyright (C) 2008 Nokia Corporation. All rights reserved. + * + * Contact: Stefan Kost <stefan.kost@nokia.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. + */ + +#include <gst/check/gstcheck.h> +#include "parser.h" + +#define SRC_CAPS_TMPL "audio/mpeg, parsed=(boolean)false, mpegversion=(int)1" +#define SINK_CAPS_TMPL "audio/mpeg, parsed=(boolean)true, mpegversion=(int)1" + +GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (SINK_CAPS_TMPL) + ); + +GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (SRC_CAPS_TMPL) + ); + +/* some data */ +static guint8 mp3_frame[384] = { + 0xff, 0xfb, 0x94, 0xc4, 0xff, 0x83, 0xc0, 0x00, + 0x01, 0xa4, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, + 0x34, 0x80, 0x00, 0x00, 0x04, 0x00, +}; + +static guint8 garbage_frame[] = { + 0xff, 0xff, 0xff, 0xff, 0xff +}; + + +GST_START_TEST (test_parse_normal) +{ + gst_parser_test_normal (mp3_frame, sizeof (mp3_frame)); +} + +GST_END_TEST; + + +GST_START_TEST (test_parse_drain_single) +{ + gst_parser_test_drain_single (mp3_frame, sizeof (mp3_frame)); +} + +GST_END_TEST; + + +GST_START_TEST (test_parse_drain_garbage) +{ + gst_parser_test_drain_garbage (mp3_frame, sizeof (mp3_frame), + garbage_frame, sizeof (garbage_frame)); +} + +GST_END_TEST; + + +GST_START_TEST (test_parse_split) +{ + gst_parser_test_split (mp3_frame, sizeof (mp3_frame)); +} + +GST_END_TEST; + + +GST_START_TEST (test_parse_skip_garbage) +{ + gst_parser_test_skip_garbage (mp3_frame, sizeof (mp3_frame), + garbage_frame, sizeof (garbage_frame)); +} + +GST_END_TEST; + + +#define structure_get_int(s,f) \ + (g_value_get_int(gst_structure_get_value(s,f))) +#define fail_unless_structure_field_int_equals(s,field,num) \ + fail_unless_equals_int (structure_get_int(s,field), num) + +GST_START_TEST (test_parse_detect_stream) +{ + GstStructure *s; + GstCaps *caps; + + caps = gst_parser_test_get_output_caps (mp3_frame, sizeof (mp3_frame), NULL); + + fail_unless (caps != NULL); + + GST_LOG ("mpegaudio output caps: %" GST_PTR_FORMAT, caps); + s = gst_caps_get_structure (caps, 0); + fail_unless (gst_structure_has_name (s, "audio/mpeg")); + fail_unless_structure_field_int_equals (s, "mpegversion", 1); + fail_unless_structure_field_int_equals (s, "layer", 3); + fail_unless_structure_field_int_equals (s, "channels", 1); + fail_unless_structure_field_int_equals (s, "rate", 48000); + + gst_caps_unref (caps); +} + +GST_END_TEST; + + +static Suite * +mpegaudioparse_suite (void) +{ + Suite *s = suite_create ("mpegaudioparse"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_parse_normal); + tcase_add_test (tc_chain, test_parse_drain_single); + tcase_add_test (tc_chain, test_parse_drain_garbage); + tcase_add_test (tc_chain, test_parse_split); + tcase_add_test (tc_chain, test_parse_skip_garbage); + tcase_add_test (tc_chain, test_parse_detect_stream); + + return s; +} + + +/* + * TODO: + * - Both push- and pull-modes need to be tested + * * Pull-mode & EOS + */ + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = mpegaudioparse_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + /* init test context */ + ctx_factory = "mpegaudioparse"; + ctx_sink_template = &sinktemplate; + ctx_src_template = &srctemplate; + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/mulawdec.c b/tests/check/elements/mulawdec.c new file mode 100644 index 0000000..94c6339 --- /dev/null +++ b/tests/check/elements/mulawdec.c @@ -0,0 +1,125 @@ +/* GStreamer MulawDec unit tests + * + * 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 + +#include <gst/check/gstcheck.h> +#include <string.h> + +static GstPad *mysrcpad, *mysinkpad; +static GstElement *mulawdec = NULL; + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw," + "format = (string) S16LE, " + "rate = (int) 8000, " + "channels = (int) 1, " "layout = (string)interleaved") + ); + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-mulaw," "rate = (int) 8000," "channels = (int) 1") + ); + +static void +mulawdec_setup (void) +{ + GstCaps *src_caps; + + src_caps = + gst_caps_from_string ("audio/x-mulaw," "rate = (int) 8000," + "channels = (int) 1"); + + GST_DEBUG ("%s", __FUNCTION__); + + mulawdec = gst_check_setup_element ("mulawdec"); + + mysrcpad = gst_check_setup_src_pad (mulawdec, &srctemplate); + mysinkpad = gst_check_setup_sink_pad (mulawdec, &sinktemplate); + + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + + gst_check_setup_events (mysrcpad, mulawdec, src_caps, GST_FORMAT_TIME); + + gst_caps_unref (src_caps); +} + +static void +buffer_unref (void *buffer, void *user_data) +{ + gst_buffer_unref (GST_BUFFER (buffer)); +} + +static void +mulawdec_teardown (void) +{ + /* free decoded buffers */ + g_list_foreach (buffers, buffer_unref, NULL); + g_list_free (buffers); + buffers = NULL; + + gst_pad_set_active (mysrcpad, FALSE); + gst_pad_set_active (mysinkpad, FALSE); + gst_check_teardown_src_pad (mulawdec); + gst_check_teardown_sink_pad (mulawdec); + gst_check_teardown_element (mulawdec); + mulawdec = NULL; +} + +GST_START_TEST (test_one_buffer) +{ + GstBuffer *buffer; + gint buf_size = 4096; + guint8 *dp; + + fail_unless (gst_element_set_state (mulawdec, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS, "could not change state to playing"); + + buffer = gst_buffer_new (); + dp = g_malloc0 (buf_size); + gst_buffer_append_memory (buffer, + gst_memory_new_wrapped (0, dp, buf_size, 0, buf_size, dp, g_free)); + ASSERT_BUFFER_REFCOUNT (buffer, "buffer", 1); + + fail_unless (gst_pad_push (mysrcpad, buffer) == GST_FLOW_OK); + + fail_unless (g_list_length (buffers) == 1); + fail_unless (gst_buffer_get_size (GST_BUFFER (g_list_first (buffers)->data))); +} + +GST_END_TEST; + +static Suite * +mulawdec_suite (void) +{ + Suite *s = suite_create ("mulawdec"); + TCase *tc_chain = tcase_create ("mulawdec"); + + tcase_add_checked_fixture (tc_chain, mulawdec_setup, mulawdec_teardown); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_one_buffer); + return s; +} + +GST_CHECK_MAIN (mulawdec) diff --git a/tests/check/elements/mulawenc.c b/tests/check/elements/mulawenc.c new file mode 100644 index 0000000..3722186 --- /dev/null +++ b/tests/check/elements/mulawenc.c @@ -0,0 +1,175 @@ +/* GStreamer MulawEnc unit tests + * + * 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 + +#include <gst/check/gstcheck.h> +#include <string.h> + +static GstPad *mysrcpad, *mysinkpad; +static GstElement *mulawenc = NULL; + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-mulaw," "rate = (int) 8000," "channels = (int) 1") + ); + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw," + "format = (string) S16LE, " + "rate = (int) 8000, " + "channels = (int) 1, " "layout = (string)interleaved") + ); + +static void +mulawenc_setup (void) +{ + GstCaps *src_caps; + + src_caps = gst_caps_from_string ("audio/x-raw," + "format = (string) S16LE, " + "rate = (int) 8000, " + "channels = (int) 1, " "layout = (string)interleaved"); + + GST_DEBUG ("%s", __FUNCTION__); + + mulawenc = gst_check_setup_element ("mulawenc"); + + mysrcpad = gst_check_setup_src_pad (mulawenc, &srctemplate); + mysinkpad = gst_check_setup_sink_pad (mulawenc, &sinktemplate); + + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + + gst_check_setup_events (mysrcpad, mulawenc, src_caps, GST_FORMAT_TIME); + gst_caps_unref (src_caps); +} + +static void +buffer_unref (void *buffer, void *user_data) +{ + gst_buffer_unref (GST_BUFFER (buffer)); +} + +static void +mulawenc_teardown (void) +{ + /* free encoded buffers */ + g_list_foreach (buffers, buffer_unref, NULL); + g_list_free (buffers); + buffers = NULL; + + gst_pad_set_active (mysrcpad, FALSE); + gst_pad_set_active (mysinkpad, FALSE); + gst_check_teardown_src_pad (mulawenc); + gst_check_teardown_sink_pad (mulawenc); + gst_check_teardown_element (mulawenc); + mulawenc = NULL; +} + +static gboolean +check_for_maximum_bitrate (GstPad * pad, GstEvent ** eventp, gpointer user_data) +{ + gboolean *found_maximum_bitrate = (gboolean *) user_data; + GstEvent *event = *eventp; + + if (event->type == GST_EVENT_TAG) { + GstTagList *taglist = NULL; + guint value = 0; + gst_event_parse_tag (event, &taglist); + + fail_unless (taglist != NULL); + + fail_unless (gst_tag_list_get_uint (taglist, GST_TAG_MAXIMUM_BITRATE, + &value)); + + /* bitrate needs to be exactly sample rate * channels * 8 */ + fail_unless (value == 8000 * 1 * 8); + + *found_maximum_bitrate = TRUE; + } + + return TRUE; +} + +GST_START_TEST (test_one_buffer) +{ + GstBuffer *buffer; + gint buf_size = 4096; + guint8 *dp; + + fail_unless (gst_element_set_state (mulawenc, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS, "could not change state to playing"); + + buffer = gst_buffer_new (); + dp = g_malloc0 (buf_size); + gst_buffer_append_memory (buffer, + gst_memory_new_wrapped (0, dp, buf_size, 0, buf_size, dp, g_free)); + ASSERT_BUFFER_REFCOUNT (buffer, "buffer", 1); + + fail_unless (gst_pad_push (mysrcpad, buffer) == GST_FLOW_OK); + + fail_unless (g_list_length (buffers) == 1); + fail_unless (gst_buffer_get_size (GST_BUFFER (g_list_first (buffers)->data))); +} + +GST_END_TEST; + +GST_START_TEST (test_tags) +{ + GstBuffer *buffer; + gint buf_size = 4096; + guint8 *dp; + gboolean found_maximum_bitrate = FALSE; + + fail_unless (gst_element_set_state (mulawenc, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS, "could not change state to playing"); + + buffer = gst_buffer_new (); + dp = g_malloc0 (buf_size); + gst_buffer_append_memory (buffer, + gst_memory_new_wrapped (0, dp, buf_size, 0, buf_size, dp, g_free)); + ASSERT_BUFFER_REFCOUNT (buffer, "buffer", 1); + + fail_unless (gst_pad_push (mysrcpad, buffer) == GST_FLOW_OK); + gst_pad_sticky_events_foreach (mysinkpad, check_for_maximum_bitrate, + &found_maximum_bitrate); + fail_unless (found_maximum_bitrate); +} + +GST_END_TEST; + +static Suite * +mulawenc_suite (void) +{ + Suite *s = suite_create ("mulawenc"); + TCase *tc_chain = tcase_create ("mulawenc"); + + tcase_add_checked_fixture (tc_chain, mulawenc_setup, mulawenc_teardown); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_one_buffer); + tcase_add_test (tc_chain, test_tags); + return s; +} + +GST_CHECK_MAIN (mulawenc) diff --git a/tests/check/elements/multifile.c b/tests/check/elements/multifile.c new file mode 100644 index 0000000..20770bb --- /dev/null +++ b/tests/check/elements/multifile.c @@ -0,0 +1,325 @@ +/* GStreamer unit test for multifile plugin + * + * Copyright (C) 2007 David A. Schleef <ds@schleef.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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <glib/gstdio.h> +#include <unistd.h> + +#include <gst/check/gstcheck.h> +#include <gst/video/video.h> +#include <stdlib.h> +#include <unistd.h> + +static void +run_pipeline (GstElement * pipeline) +{ + gst_element_set_state (pipeline, GST_STATE_PAUSED); + gst_element_get_state (pipeline, NULL, NULL, -1); + gst_element_set_state (pipeline, GST_STATE_PLAYING); + /* FIXME too lazy */ + g_usleep (1000000); + gst_element_set_state (pipeline, GST_STATE_NULL); +} + +GST_START_TEST (test_multifilesink_key_frame) +{ + GstElement *pipeline; + GstElement *mfs; + int i; + const gchar *tmpdir; + gchar *my_tmpdir; + gchar *template; + gchar *mfs_pattern; + + tmpdir = g_get_tmp_dir (); + template = g_build_filename (tmpdir, "multifile-test-XXXXXX", NULL); + my_tmpdir = g_mkdtemp (template); + fail_if (my_tmpdir == NULL); + + pipeline = + gst_parse_launch + ("videotestsrc num-buffers=10 ! video/x-raw,format=(string)I420,width=320,height=240 ! multifilesink name=mfs", + NULL); + fail_if (pipeline == NULL); + mfs = gst_bin_get_by_name (GST_BIN (pipeline), "mfs"); + fail_if (mfs == NULL); + mfs_pattern = g_build_filename (my_tmpdir, "%05d", NULL); + g_object_set (G_OBJECT (mfs), "location", mfs_pattern, NULL); + g_object_unref (mfs); + run_pipeline (pipeline); + gst_object_unref (pipeline); + + for (i = 0; i < 10; i++) { + char *s; + + s = g_strdup_printf (mfs_pattern, i); + fail_if (g_remove (s) != 0); + g_free (s); + } + fail_if (g_remove (my_tmpdir) != 0); + + g_free (mfs_pattern); + g_free (my_tmpdir); +} + +GST_END_TEST; + +GST_START_TEST (test_multifilesink_max_files) +{ + GstElement *pipeline; + GstElement *mfs; + int i; + const gchar *tmpdir; + gchar *my_tmpdir; + gchar *template; + gchar *mfs_pattern; + + tmpdir = g_get_tmp_dir (); + template = g_build_filename (tmpdir, "multifile-test-XXXXXX", NULL); + my_tmpdir = g_mkdtemp (template); + fail_if (my_tmpdir == NULL); + + pipeline = + gst_parse_launch + ("videotestsrc num-buffers=10 ! video/x-raw,format=(string)I420,width=320,height=240 ! multifilesink name=mfs", + NULL); + fail_if (pipeline == NULL); + mfs = gst_bin_get_by_name (GST_BIN (pipeline), "mfs"); + fail_if (mfs == NULL); + mfs_pattern = g_build_filename (my_tmpdir, "%05d", NULL); + g_object_set (G_OBJECT (mfs), "location", mfs_pattern, "max-files", 3, NULL); + g_object_unref (mfs); + run_pipeline (pipeline); + gst_object_unref (pipeline); + + for (i = 0; i < 7; i++) { + char *s; + + s = g_strdup_printf (mfs_pattern, i); + fail_unless (g_remove (s) != 0); + g_free (s); + } + for (i = 7; i < 10; i++) { + char *s; + + s = g_strdup_printf (mfs_pattern, i); + fail_if (g_remove (s) != 0); + g_free (s); + } + fail_if (g_remove (my_tmpdir) != 0); + + g_free (mfs_pattern); + g_free (my_tmpdir); +} + +GST_END_TEST; + +GST_START_TEST (test_multifilesink_key_unit) +{ + GstElement *mfs; + int i; + const gchar *tmpdir; + gchar *my_tmpdir; + gchar *template; + gchar *mfs_pattern; + GstBuffer *buf; + GstPad *sink; + GstSegment segment; + + tmpdir = g_get_tmp_dir (); + template = g_build_filename (tmpdir, "multifile-test-XXXXXX", NULL); + my_tmpdir = g_mkdtemp (template); + fail_if (my_tmpdir == NULL); + + mfs = gst_element_factory_make ("multifilesink", NULL); + fail_if (mfs == NULL); + mfs_pattern = g_build_filename (my_tmpdir, "%05d", NULL); + g_object_set (G_OBJECT (mfs), "location", mfs_pattern, "next-file", 3, NULL); + fail_if (gst_element_set_state (mfs, + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + sink = gst_element_get_static_pad (mfs, "sink"); + + gst_pad_send_event (sink, gst_event_new_stream_start ("test")); + gst_segment_init (&segment, GST_FORMAT_TIME); + gst_pad_send_event (sink, gst_event_new_segment (&segment)); + + buf = gst_buffer_new_and_alloc (4); + + gst_buffer_fill (buf, 0, "foo", 4); + fail_if (gst_pad_chain (sink, gst_buffer_copy (buf)) != GST_FLOW_OK); + + gst_buffer_fill (buf, 0, "bar", 4); + fail_if (gst_pad_chain (sink, gst_buffer_copy (buf)) != GST_FLOW_OK); + + fail_unless (gst_pad_send_event (sink, + gst_video_event_new_downstream_force_key_unit (GST_CLOCK_TIME_NONE, + GST_CLOCK_TIME_NONE, GST_CLOCK_TIME_NONE, TRUE, 1))); + + gst_buffer_fill (buf, 0, "baz", 4); + fail_if (gst_pad_chain (sink, buf) != GST_FLOW_OK); + + fail_if (gst_element_set_state (mfs, + GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE); + + for (i = 0; i < 2; i++) { + char *s; + + s = g_strdup_printf (mfs_pattern, i); + fail_if (g_remove (s) != 0); + g_free (s); + } + fail_if (g_remove (my_tmpdir) != 0); + + g_free (mfs_pattern); + g_free (my_tmpdir); + gst_object_unref (sink); + gst_object_unref (mfs); +} + +GST_END_TEST; + +GST_START_TEST (test_multifilesrc) +{ + GstElement *pipeline; + GstElement *mfs; + int i; + const gchar *tmpdir; + gchar *my_tmpdir; + gchar *template; + gchar *mfs_pattern; + + tmpdir = g_get_tmp_dir (); + template = g_build_filename (tmpdir, "multifile-test-XXXXXX", NULL); + my_tmpdir = g_mkdtemp (template); + fail_if (my_tmpdir == NULL); + + pipeline = + gst_parse_launch + ("videotestsrc num-buffers=10 ! video/x-raw,format=(string)I420,width=320,height=240 ! multifilesink name=mfs", + NULL); + fail_if (pipeline == NULL); + mfs = gst_bin_get_by_name (GST_BIN (pipeline), "mfs"); + fail_if (mfs == NULL); + mfs_pattern = g_build_filename (my_tmpdir, "%05d", NULL); + g_object_set (G_OBJECT (mfs), "location", mfs_pattern, NULL); + g_free (mfs_pattern); + g_object_unref (mfs); + run_pipeline (pipeline); + gst_object_unref (pipeline); + + pipeline = + gst_parse_launch + ("multifilesrc ! video/x-raw,format=(string)I420,width=320,height=240,framerate=10/1 ! fakesink", + NULL); + fail_if (pipeline == NULL); + mfs = gst_bin_get_by_name (GST_BIN (pipeline), "multifilesrc0"); + fail_if (mfs == NULL); + mfs_pattern = g_build_filename (my_tmpdir, "%05d", NULL); + g_object_set (G_OBJECT (mfs), "location", mfs_pattern, NULL); + g_object_unref (mfs); + run_pipeline (pipeline); + gst_object_unref (pipeline); + + for (i = 0; i < 10; i++) { + char *s; + + s = g_strdup_printf (mfs_pattern, i); + fail_if (g_remove (s) != 0); + g_free (s); + } + fail_if (g_remove (my_tmpdir) != 0); + + g_free (mfs_pattern); + g_free (my_tmpdir); +} + +GST_END_TEST; + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +/* make sure stop_index is honoured even if the next target file exists */ +GST_START_TEST (test_multifilesrc_stop_index) +{ + GstElement *src; + GstEvent *event; + GstPad *sinkpad; + gchar *fn; + + src = gst_check_setup_element ("multifilesrc"); + fail_unless (src != NULL); + + fn = g_build_filename (GST_TEST_FILES_PATH, "image.jpg", NULL); + g_object_set (src, "location", fn, NULL); + g_free (fn); + + g_object_set (src, "stop-index", 5, NULL); + + sinkpad = gst_check_setup_sink_pad_by_name (src, &sinktemplate, "src"); + fail_unless (sinkpad != NULL); + gst_pad_set_active (sinkpad, TRUE); + + gst_element_set_state (src, GST_STATE_PLAYING); + + gst_element_get_state (src, NULL, NULL, -1); + + /* busy-loop for EOS */ + do { + g_usleep (G_USEC_PER_SEC / 10); + event = gst_pad_get_sticky_event (sinkpad, GST_EVENT_EOS, 0); + } while (event == NULL); + gst_event_unref (event); + + /* Range appears to be [ start, stop ] */ + fail_unless_equals_int (g_list_length (buffers), 5 + 1); + + gst_element_set_state (src, GST_STATE_NULL); + + gst_check_teardown_pad_by_name (src, "src"); + gst_check_teardown_element (src); +} + +GST_END_TEST; + + +static Suite * +multifile_suite (void) +{ + Suite *s = suite_create ("multifile"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_multifilesink_key_frame); + tcase_add_test (tc_chain, test_multifilesink_max_files); + tcase_add_test (tc_chain, test_multifilesink_key_unit); + tcase_add_test (tc_chain, test_multifilesrc); + tcase_add_test (tc_chain, test_multifilesrc_stop_index); + + return s; +} + +GST_CHECK_MAIN (multifile); diff --git a/tests/check/elements/parser.c b/tests/check/elements/parser.c new file mode 100644 index 0000000..52ffed8 --- /dev/null +++ b/tests/check/elements/parser.c @@ -0,0 +1,435 @@ +/* + * GStreamer + * + * unit test for (audio) parser + * + * Copyright (C) 2008 Nokia Corporation. All rights reserved. + * + * Contact: Stefan Kost <stefan.kost@nokia.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. + */ + +#include <gst/check/gstcheck.h> +#include "elements/parser.h" + + +/* context state variables */ +const gchar *ctx_factory; +GstStaticPadTemplate *ctx_sink_template; +GstStaticPadTemplate *ctx_src_template; +GstCaps *ctx_input_caps; +GstCaps *ctx_output_caps; +guint ctx_discard = 0; +datablob ctx_headers[MAX_HEADERS] = { {NULL, 0}, }; + +gboolean ctx_no_metadata = FALSE; + +/* helper variables */ +GList *current_buf = NULL; + +GstPad *srcpad, *sinkpad; +guint dataoffset = 0; +GstClockTime ts_counter = 0; +gint64 offset_counter = 0; +guint buffer_counter = 0; + +typedef struct +{ + guint discard; + guint buffers_before_offset_skip; + guint offset_skip_amount; + const guint8 *data_to_verify; + guint data_to_verify_size; + GstCaps *caps; + gboolean no_metadata; +} buffer_verify_data_s; + +/* takes a copy of the passed buffer data */ +static GstBuffer * +buffer_new (const unsigned char *buffer_data, guint size) +{ + GstBuffer *buffer; + + buffer = gst_buffer_new_and_alloc (size); + if (buffer_data) { + gst_buffer_fill (buffer, 0, buffer_data, size); + } else { + guint i; + GstMapInfo map; + + gst_buffer_map (buffer, &map, GST_MAP_WRITE); + /* Create a recognizable pattern (loop 0x00 -> 0xff) in the data block */ + for (i = 0; i < map.size; i++) { + map.data[i] = i % 0x100; + } + gst_buffer_unmap (buffer, &map); + } + + /* gst_buffer_set_caps (buffer, GST_PAD_CAPS (srcpad)); */ + GST_BUFFER_OFFSET (buffer) = dataoffset; + dataoffset += size; + return buffer; +} + +/* + * Adds buffer sizes together. + */ +static void +buffer_count_size (void *buffer, void *user_data) +{ + guint *sum = (guint *) user_data; + *sum += gst_buffer_get_size (buffer); +} + +/* + * Verify that given buffer contains predefined ADTS frame. + */ +static void +buffer_verify_data (void *buffer, void *user_data) +{ + buffer_verify_data_s *vdata; + + if (!user_data) { + return; + } + + vdata = (buffer_verify_data_s *) user_data; + + GST_DEBUG ("discard: %d", vdata->discard); + if (vdata->discard) { + buffer_counter++; + if (buffer_counter == vdata->discard) { + buffer_counter = 0; + vdata->discard = 0; + } + return; + } + + fail_unless (gst_buffer_get_size (buffer) == vdata->data_to_verify_size); + fail_unless (gst_buffer_memcmp (buffer, 0, vdata->data_to_verify, + vdata->data_to_verify_size) == 0); + + if (vdata->buffers_before_offset_skip) { + /* This is for skipping the garbage in some test cases */ + if (buffer_counter == vdata->buffers_before_offset_skip) { + offset_counter += vdata->offset_skip_amount; + } + } + if (!vdata->no_metadata) { + fail_unless (GST_BUFFER_TIMESTAMP (buffer) == ts_counter); + fail_unless (GST_BUFFER_DURATION (buffer) != 0); + fail_unless (GST_BUFFER_OFFSET (buffer) == offset_counter); + } + + ts_counter += GST_BUFFER_DURATION (buffer); + offset_counter += gst_buffer_get_size (buffer); + buffer_counter++; +} + +static GstElement * +setup_element (const gchar * factory, GstStaticPadTemplate * sink_template, + GstCaps * sink_caps, GstStaticPadTemplate * src_template, + GstCaps * src_caps) +{ + GstElement *element; + GstBus *bus; + gchar *caps_str = NULL; + + element = gst_check_setup_element (factory); + srcpad = gst_check_setup_src_pad (element, src_template); + if (sink_caps) { + caps_str = gst_caps_to_string (sink_caps); + sink_template->static_caps.string = caps_str; + } + sinkpad = gst_check_setup_sink_pad (element, sink_template); + gst_pad_set_active (srcpad, TRUE); + gst_check_setup_events (srcpad, element, src_caps, GST_FORMAT_BYTES); + gst_pad_set_active (sinkpad, TRUE); + + bus = gst_bus_new (); + gst_element_set_bus (element, bus); + + fail_unless (gst_element_set_state (element, + GST_STATE_PLAYING) != GST_STATE_CHANGE_FAILURE, + "could not set to playing"); + + ts_counter = offset_counter = buffer_counter = 0; + buffers = NULL; + g_free (caps_str); + return element; +} + +static void +cleanup_element (GstElement * element) +{ + GstBus *bus; + + /* Free parsed buffers */ + gst_check_drop_buffers (); + + bus = GST_ELEMENT_BUS (element); + gst_bus_set_flushing (bus, TRUE); + gst_object_unref (bus); + + gst_pad_set_active (srcpad, FALSE); + gst_pad_set_active (sinkpad, FALSE); + gst_check_teardown_src_pad (element); + gst_check_teardown_sink_pad (element); + gst_check_teardown_element (element); +} + +/* inits a standard test */ +void +gst_parser_test_init (GstParserTest * ptest, guint8 * data, guint size, + guint num) +{ + /* need these */ + fail_unless (ctx_factory != NULL); + fail_unless (ctx_src_template != NULL); + fail_unless (ctx_sink_template != NULL); + + /* basics */ + memset (ptest, 0, sizeof (*ptest)); + ptest->factory = ctx_factory; + ptest->sink_template = ctx_sink_template; + ptest->src_template = ctx_src_template; + ptest->framed = TRUE; + /* could be NULL if not relevant/needed */ + ptest->src_caps = ctx_input_caps; + ptest->sink_caps = ctx_output_caps; + memcpy (ptest->headers, ctx_headers, sizeof (ptest->headers)); + ptest->discard = ctx_discard; + /* some data that pleases caller */ + ptest->series[0].data = data; + ptest->series[0].size = size; + ptest->series[0].num = num; + ptest->series[0].fpb = 1; + ptest->series[1].fpb = 1; + ptest->series[2].fpb = 1; + ptest->no_metadata = ctx_no_metadata; +} + +/* + * Test if the parser pushes clean data properly. + */ +void +gst_parser_test_run (GstParserTest * test, GstCaps ** out_caps) +{ + buffer_verify_data_s vdata = { 0, 0, 0, NULL, 0, NULL, FALSE }; + GstElement *element; + GstBuffer *buffer = NULL; + GstCaps *src_caps; + guint i, j, k; + guint frames = 0, size = 0; + + element = setup_element (test->factory, test->sink_template, NULL, + test->src_template, test->src_caps); + + /* push some setup headers */ + for (j = 0; j < G_N_ELEMENTS (test->headers) && test->headers[j].data; j++) { + buffer = buffer_new (test->headers[j].data, test->headers[j].size); + fail_unless_equals_int (gst_pad_push (srcpad, buffer), GST_FLOW_OK); + } + + for (j = 0; j < 3; j++) { + for (i = 0; i < test->series[j].num; i++) { + /* sanity enforcing */ + for (k = 0; k < MAX (1, test->series[j].fpb); k++) { + if (!k) + buffer = buffer_new (test->series[j].data, test->series[j].size); + else { + buffer = gst_buffer_append (buffer, + buffer_new (test->series[j].data, test->series[j].size)); + } + } + fail_unless_equals_int (gst_pad_push (srcpad, buffer), GST_FLOW_OK); + if (j == 0) + vdata.buffers_before_offset_skip++; + else if (j == 1) + vdata.offset_skip_amount += test->series[j].size * test->series[j].fpb; + if (j != 1) { + frames += test->series[j].fpb; + size += test->series[j].size * test->series[j].fpb; + } + } + } + gst_pad_push_event (srcpad, gst_event_new_eos ()); + + if (G_LIKELY (test->framed)) + fail_unless_equals_int (g_list_length (buffers) - test->discard, frames); + + /* if all frames are identical, do extended test, + * otherwise only verify total data size */ + if (test->series[0].data && (!test->series[2].size || + (test->series[0].size == test->series[2].size && test->series[2].data + && !memcmp (test->series[0].data, test->series[2].data, + test->series[0].size)))) { + vdata.data_to_verify = test->series[0].data; + vdata.data_to_verify_size = test->series[0].size; + vdata.caps = test->sink_caps; + vdata.discard = test->discard; + vdata.no_metadata = test->no_metadata; + g_list_foreach (buffers, buffer_verify_data, &vdata); + } else { + guint datasum = 0; + + g_list_foreach (buffers, buffer_count_size, &datasum); + size -= test->dropped; + fail_unless_equals_int (datasum, size); + } + + src_caps = gst_pad_get_current_caps (sinkpad); + GST_LOG ("output caps: %" GST_PTR_FORMAT, src_caps); + + if (test->sink_caps) { + GST_LOG ("%" GST_PTR_FORMAT " = %" GST_PTR_FORMAT " ?", src_caps, + test->sink_caps); + fail_unless (gst_caps_is_equal (src_caps, test->sink_caps)); + } + + if (out_caps) + *out_caps = src_caps; + else + gst_caps_unref (src_caps); + + cleanup_element (element); +} + +/* + * Test if the parser pushes clean data properly. + */ +void +gst_parser_test_normal (guint8 * data, guint size) +{ + GstParserTest ptest; + + gst_parser_test_init (&ptest, data, size, 10); + gst_parser_test_run (&ptest, NULL); +} + +/* + * Test if parser drains its buffers properly. Even one single frame + * should be drained and pushed forward when EOS occurs. This single frame + * case is special, since normally the parser needs more data to be sure + * about stream format. But it should still push the frame forward in EOS. + */ +void +gst_parser_test_drain_single (guint8 * data, guint size) +{ + GstParserTest ptest; + + gst_parser_test_init (&ptest, data, size, 1); + gst_parser_test_run (&ptest, NULL); +} + +/* + * Make sure that parser does not drain garbage when EOS occurs. + */ +void +gst_parser_test_drain_garbage (guint8 * data, guint size, guint8 * garbage, + guint gsize) +{ + GstParserTest ptest; + + /* provide enough initial frames since it may take some parsers some + * time to be convinced of proper sync */ + gst_parser_test_init (&ptest, data, size, 10); + ptest.series[1].data = garbage; + ptest.series[1].size = gsize; + ptest.series[1].num = 1; + gst_parser_test_run (&ptest, NULL); +} + +/* + * Test if parser splits a buffer that contains two frames into two + * separate buffers properly. + */ +void +gst_parser_test_split (guint8 * data, guint size) +{ + GstParserTest ptest; + + gst_parser_test_init (&ptest, data, size, 10); + ptest.series[0].fpb = 2; + gst_parser_test_run (&ptest, NULL); +} + +/* + * Test if the parser skips garbage between frames properly. + */ +void +gst_parser_test_skip_garbage (guint8 * data, guint size, guint8 * garbage, + guint gsize) +{ + GstParserTest ptest; + + gst_parser_test_init (&ptest, data, size, 10); + ptest.series[1].data = garbage; + ptest.series[1].size = gsize; + ptest.series[1].num = 1; + ptest.series[2].data = data; + ptest.series[2].size = size; + ptest.series[2].num = 10; + gst_parser_test_run (&ptest, NULL); +} + +/* + * Test if the src caps are set according to stream format. + */ +void +gst_parser_test_output_caps (guint8 * data, guint size, + const gchar * input_caps, const gchar * output_caps) +{ + GstParserTest ptest; + + gst_parser_test_init (&ptest, data, size, 10); + if (input_caps) { + ptest.src_caps = gst_caps_from_string (input_caps); + fail_unless (ptest.src_caps != NULL); + } + if (output_caps) { + ptest.sink_caps = gst_caps_from_string (output_caps); + fail_unless (ptest.sink_caps != NULL); + } + gst_parser_test_run (&ptest, NULL); + if (ptest.sink_caps) + gst_caps_unref (ptest.sink_caps); + if (ptest.src_caps) + gst_caps_unref (ptest.src_caps); +} + +/* + * Test if the src caps are set according to stream format. + */ +GstCaps * +gst_parser_test_get_output_caps (guint8 * data, guint size, + const gchar * input_caps) +{ + GstParserTest ptest; + GstCaps *out_caps; + + gst_parser_test_init (&ptest, data, size, 10); + if (input_caps) { + ptest.src_caps = gst_caps_from_string (input_caps); + fail_unless (ptest.src_caps != NULL); + } + gst_parser_test_run (&ptest, &out_caps); + if (ptest.src_caps) + gst_caps_unref (ptest.src_caps); + + return out_caps; +} diff --git a/tests/check/elements/parser.h b/tests/check/elements/parser.h new file mode 100644 index 0000000..c4867cd --- /dev/null +++ b/tests/check/elements/parser.h @@ -0,0 +1,95 @@ +/* + * GStreamer + * + * unit test for (audio) parser + * + * Copyright (C) 2008 Nokia Corporation. All rights reserved. + * + * Contact: Stefan Kost <stefan.kost@nokia.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. + */ + +#include <gst/check/gstcheck.h> + +#define MAX_HEADERS 10 + +typedef struct { + guint8 *data; + guint size; +} datablob; + +/* context state variables; to be set by test using this helper */ +/* mandatory */ +extern const gchar *ctx_factory; +extern GstStaticPadTemplate *ctx_sink_template; +extern GstStaticPadTemplate *ctx_src_template; +/* optional */ +extern GstCaps *ctx_input_caps; +extern GstCaps *ctx_output_caps; +extern guint ctx_discard; +extern datablob ctx_headers[MAX_HEADERS]; +extern gboolean ctx_no_metadata; + +/* no refs taken/kept, all up to caller */ +typedef struct +{ + const gchar *factory; + GstStaticPadTemplate *sink_template; + GstStaticPadTemplate *src_template; + /* caps that go into element */ + GstCaps *src_caps; + /* optional: output caps to verify */ + GstCaps *sink_caps; + /* initial headers */ + datablob headers[MAX_HEADERS]; + /* initial (header) output to forego checking */ + guint discard; + /* series of buffers; middle series considered garbage */ + struct { + /* data and size */ + guint8 *data; + guint size; + /* num of frames with above data per buffer */ + guint fpb; + /* num of buffers */ + guint num; + } series[3]; + /* sigh, weird cases */ + gboolean framed; + guint dropped; + gboolean no_metadata; +} GstParserTest; + +void gst_parser_test_init (GstParserTest * ptest, guint8 * data, guint size, guint num); + +void gst_parser_test_run (GstParserTest * test, GstCaps ** out_caps); + +void gst_parser_test_normal (guint8 *data, guint size); + +void gst_parser_test_drain_single (guint8 *data, guint size); + +void gst_parser_test_drain_garbage (guint8 *data, guint size, guint8 *garbage, guint gsize); + +void gst_parser_test_split (guint8 *data, guint size); + +void gst_parser_test_skip_garbage (guint8 *data, guint size, guint8 *garbage, guint gsize); + +void gst_parser_test_output_caps (guint8 *data, guint size, const gchar * input_caps, + const gchar * output_caps); + +GstCaps *gst_parser_test_get_output_caps (guint8 *data, guint size, const gchar * input_caps); + diff --git a/tests/check/elements/qtmux.c b/tests/check/elements/qtmux.c new file mode 100755 index 0000000..105b1e8 --- /dev/null +++ b/tests/check/elements/qtmux.c @@ -0,0 +1,932 @@ +/* GStreamer + * + * unit test for qtmux + * + * Copyright (C) <2008> Mark Nauwelaerts <mnauw@users.sf.net> + * + * 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 + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <glib/gstdio.h> + +#include <gst/check/gstcheck.h> +#include <gst/pbutils/encoding-profile.h> + +/* 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; + +#define AUDIO_CAPS_STRING "audio/mpeg, " \ + "mpegversion = (int) 1, " \ + "layer = (int) 3, " \ + "channels = (int) 2, " \ + "rate = (int) 48000" + +#define AUDIO_AAC_CAPS_STRING "audio/mpeg, " \ + "mpegversion=(int)4, " \ + "channels=(int)1, " \ + "rate=(int)44100, " \ + "stream-format=(string)raw, " \ + "level=(string)2, " \ + "base-profile=(string)lc, " \ + "profile=(string)lc, " \ + "codec_data=(buffer)1208" + +#define VIDEO_CAPS_STRING "video/mpeg, " \ + "mpegversion = (int) 4, " \ + "systemstream = (boolean) false, " \ + "width = (int) 384, " \ + "height = (int) 288, " \ + "framerate = (fraction) 25/1" + +#define VIDEO_CAPS_H264_STRING "video/x-h264, " \ + "width=(int)320, " \ + "height=(int)240, " \ + "framerate=(fraction)30/1, " \ + "pixel-aspect-ratio=(fraction)1/1, " \ + "codec_data=(buffer)01640014ffe1001867640014a" \ + "cd94141fb0110000003001773594000f14299600" \ + "1000568ebecb22c, " \ + "stream-format=(string)avc, " \ + "alignment=(string)au, " \ + "level=(string)2, " \ + "profile=(string)high" + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/quicktime")); +static GstStaticPadTemplate srcvideotemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (VIDEO_CAPS_STRING)); + +static GstStaticPadTemplate srcvideoh264template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (VIDEO_CAPS_H264_STRING)); + +static GstStaticPadTemplate srcaudiotemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (AUDIO_CAPS_STRING)); + +static GstStaticPadTemplate srcaudioaactemplate = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (AUDIO_AAC_CAPS_STRING)); + +/* setup and teardown needs some special handling for muxer */ +static GstPad * +setup_src_pad (GstElement * element, + GstStaticPadTemplate * template, const gchar * sinkname) +{ + GstPad *srcpad, *sinkpad; + + GST_DEBUG_OBJECT (element, "setting up sending pad"); + /* sending pad */ + srcpad = gst_pad_new_from_static_template (template, "src"); + fail_if (srcpad == NULL, "Could not create a srcpad"); + ASSERT_OBJECT_REFCOUNT (srcpad, "srcpad", 1); + + if (!(sinkpad = gst_element_get_static_pad (element, sinkname))) + sinkpad = gst_element_get_request_pad (element, sinkname); + fail_if (sinkpad == NULL, "Could not get sink pad from %s", + GST_ELEMENT_NAME (element)); + /* references are owned by: 1) us, 2) qtmux, 3) collect pads */ + ASSERT_OBJECT_REFCOUNT (sinkpad, "sinkpad", 3); + fail_unless (gst_pad_link (srcpad, sinkpad) == GST_PAD_LINK_OK, + "Could not link source and %s sink pads", GST_ELEMENT_NAME (element)); + gst_object_unref (sinkpad); /* because we got it higher up */ + + /* references are owned by: 1) qtmux, 2) collect pads */ + ASSERT_OBJECT_REFCOUNT (sinkpad, "sinkpad", 2); + + return srcpad; +} + +static void +teardown_src_pad (GstPad * srcpad) +{ + GstPad *sinkpad; + + /* clean up floating src pad */ + sinkpad = gst_pad_get_peer (srcpad); + fail_if (sinkpad == NULL); + /* pad refs held by 1) qtmux 2) collectpads and 3) us (through _get_peer) */ + ASSERT_OBJECT_REFCOUNT (sinkpad, "sinkpad", 3); + + gst_pad_unlink (srcpad, sinkpad); + + /* after unlinking, pad refs still held by + * 1) qtmux and 2) collectpads and 3) us (through _get_peer) */ + ASSERT_OBJECT_REFCOUNT (sinkpad, "sinkpad", 3); + gst_object_unref (sinkpad); + /* one more ref is held by element itself */ + + /* pad refs held by creator */ + ASSERT_OBJECT_REFCOUNT (srcpad, "srcpad", 1); + gst_object_unref (srcpad); +} + +static GstElement * +setup_qtmux (GstStaticPadTemplate * srctemplate, const gchar * sinkname) +{ + GstElement *qtmux; + + GST_DEBUG ("setup_qtmux"); + qtmux = gst_check_setup_element ("qtmux"); + mysrcpad = setup_src_pad (qtmux, srctemplate, sinkname); + mysinkpad = gst_check_setup_sink_pad (qtmux, &sinktemplate); + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + + return qtmux; +} + +static void +cleanup_qtmux (GstElement * qtmux, const gchar * sinkname) +{ + GST_DEBUG ("cleanup_qtmux"); + gst_element_set_state (qtmux, GST_STATE_NULL); + + gst_pad_set_active (mysrcpad, FALSE); + gst_pad_set_active (mysinkpad, FALSE); + teardown_src_pad (mysrcpad); + gst_check_teardown_sink_pad (qtmux); + gst_check_teardown_element (qtmux); +} + +static void +check_qtmux_pad (GstStaticPadTemplate * srctemplate, const gchar * sinkname, + guint32 dts_method) +{ + GstElement *qtmux; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + int num_buffers; + int i; + guint8 data0[12] = "\000\000\000\024ftypqt "; + guint8 data1[8] = "\000\000\000\001mdat"; + guint8 data2[4] = "moov"; + GstSegment segment; + + qtmux = setup_qtmux (srctemplate, sinkname); + g_object_set (qtmux, "dts-method", dts_method, NULL); + fail_unless (gst_element_set_state (qtmux, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + gst_pad_push_event (mysrcpad, gst_event_new_stream_start ("test")); + + caps = gst_pad_get_pad_template_caps (mysrcpad); + gst_pad_set_caps (mysrcpad, caps); + gst_caps_unref (caps); + + /* ensure segment (format) properly setup */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + inbuffer = gst_buffer_new_and_alloc (1); + gst_buffer_memset (inbuffer, 0, 0, 1); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + GST_BUFFER_DURATION (inbuffer) = 40 * GST_MSECOND; + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + + /* send eos to have moov written */ + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ()) == TRUE); + + num_buffers = g_list_length (buffers); + /* at least expect ftyp, mdat header, buffer chunk and moov */ + fail_unless (num_buffers >= 4); + + /* clean up first to clear any pending refs in sticky caps */ + cleanup_qtmux (qtmux, sinkname); + + for (i = 0; i < num_buffers; ++i) { + outbuffer = GST_BUFFER (buffers->data); + fail_if (outbuffer == NULL); + buffers = g_list_remove (buffers, outbuffer); + + switch (i) { + case 0: + { + /* ftyp header */ + fail_unless (gst_buffer_get_size (outbuffer) >= 20); + fail_unless (gst_buffer_memcmp (outbuffer, 0, data0, + sizeof (data0)) == 0); + fail_unless (gst_buffer_memcmp (outbuffer, 16, data0 + 8, 4) == 0); + break; + } + case 1: /* mdat header */ + fail_unless (gst_buffer_get_size (outbuffer) == 16); + fail_unless (gst_buffer_memcmp (outbuffer, 0, data1, sizeof (data1)) + == 0); + break; + case 2: /* buffer we put in */ + fail_unless (gst_buffer_get_size (outbuffer) == 1); + break; + case 3: /* moov */ + fail_unless (gst_buffer_get_size (outbuffer) > 8); + fail_unless (gst_buffer_memcmp (outbuffer, 4, data2, + sizeof (data2)) == 0); + break; + default: + break; + } + + ASSERT_BUFFER_REFCOUNT (outbuffer, "outbuffer", 1); + gst_buffer_unref (outbuffer); + outbuffer = NULL; + } + + g_list_free (buffers); + buffers = NULL; +} + +static void +check_qtmux_pad_fragmented (GstStaticPadTemplate * srctemplate, + const gchar * sinkname, guint32 dts_method, gboolean streamable) +{ + GstElement *qtmux; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + int num_buffers; + int i; + guint8 data0[12] = "\000\000\000\024ftypqt "; + guint8 data1[4] = "mdat"; + guint8 data2[4] = "moov"; + guint8 data3[4] = "moof"; + guint8 data4[4] = "mfra"; + GstSegment segment; + + qtmux = setup_qtmux (srctemplate, sinkname); + g_object_set (qtmux, "dts-method", dts_method, NULL); + g_object_set (qtmux, "fragment-duration", 2000, NULL); + g_object_set (qtmux, "streamable", streamable, NULL); + fail_unless (gst_element_set_state (qtmux, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + gst_pad_push_event (mysrcpad, gst_event_new_stream_start ("test")); + + caps = gst_pad_get_pad_template_caps (mysrcpad); + gst_pad_set_caps (mysrcpad, caps); + gst_caps_unref (caps); + + /* ensure segment (format) properly setup */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + inbuffer = gst_buffer_new_and_alloc (1); + gst_buffer_memset (inbuffer, 0, 0, 1); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + GST_BUFFER_DURATION (inbuffer) = 40 * GST_MSECOND; + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + + /* send eos to have all written */ + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ()) == TRUE); + + num_buffers = g_list_length (buffers); + /* at least expect ftyp, moov, moof, mdat header, buffer chunk + * and optionally mfra */ + fail_unless (num_buffers >= 5); + + /* clean up first to clear any pending refs in sticky caps */ + cleanup_qtmux (qtmux, sinkname); + + for (i = 0; i < num_buffers; ++i) { + outbuffer = GST_BUFFER (buffers->data); + fail_if (outbuffer == NULL); + buffers = g_list_remove (buffers, outbuffer); + + switch (i) { + case 0: + { + /* ftyp header */ + fail_unless (gst_buffer_get_size (outbuffer) >= 20); + fail_unless (gst_buffer_memcmp (outbuffer, 0, data0, + sizeof (data0)) == 0); + fail_unless (gst_buffer_memcmp (outbuffer, 16, data0 + 8, 4) == 0); + break; + } + case 1: /* moov */ + fail_unless (gst_buffer_get_size (outbuffer) > 8); + fail_unless (gst_buffer_memcmp (outbuffer, 4, data2, + sizeof (data2)) == 0); + break; + case 2: /* moof */ + fail_unless (gst_buffer_get_size (outbuffer) > 8); + fail_unless (gst_buffer_memcmp (outbuffer, 4, data3, + sizeof (data3)) == 0); + break; + case 3: /* mdat header */ + fail_unless (gst_buffer_get_size (outbuffer) == 8); + fail_unless (gst_buffer_memcmp (outbuffer, 4, data1, + sizeof (data1)) == 0); + break; + case 4: /* buffer we put in */ + fail_unless (gst_buffer_get_size (outbuffer) == 1); + break; + case 5: /* mfra */ + fail_unless (gst_buffer_get_size (outbuffer) > 8); + fail_unless (gst_buffer_memcmp (outbuffer, 4, data4, + sizeof (data4)) == 0); + break; + default: + break; + } + + ASSERT_BUFFER_REFCOUNT (outbuffer, "outbuffer", 1); + gst_buffer_unref (outbuffer); + outbuffer = NULL; + } + + g_list_free (buffers); + buffers = NULL; +} + +/* dts-method dd */ + +GST_START_TEST (test_video_pad_dd) +{ + check_qtmux_pad (&srcvideotemplate, "video_%u", 0); +} + +GST_END_TEST; + +GST_START_TEST (test_audio_pad_dd) +{ + check_qtmux_pad (&srcaudiotemplate, "audio_%u", 0); +} + +GST_END_TEST; + + +GST_START_TEST (test_video_pad_frag_dd) +{ + check_qtmux_pad_fragmented (&srcvideotemplate, "video_%u", 0, FALSE); +} + +GST_END_TEST; + +GST_START_TEST (test_audio_pad_frag_dd) +{ + check_qtmux_pad_fragmented (&srcaudiotemplate, "audio_%u", 0, FALSE); +} + +GST_END_TEST; + + +GST_START_TEST (test_video_pad_frag_dd_streamable) +{ + check_qtmux_pad_fragmented (&srcvideotemplate, "video_%u", 0, TRUE); +} + +GST_END_TEST; + + +GST_START_TEST (test_audio_pad_frag_dd_streamable) +{ + check_qtmux_pad_fragmented (&srcaudiotemplate, "audio_%u", 0, TRUE); +} + +GST_END_TEST; + +/* dts-method reorder */ + +GST_START_TEST (test_video_pad_reorder) +{ + check_qtmux_pad (&srcvideotemplate, "video_%u", 1); +} + +GST_END_TEST; + +GST_START_TEST (test_audio_pad_reorder) +{ + check_qtmux_pad (&srcaudiotemplate, "audio_%u", 1); +} + +GST_END_TEST; + + +GST_START_TEST (test_video_pad_frag_reorder) +{ + check_qtmux_pad_fragmented (&srcvideotemplate, "video_%u", 1, FALSE); +} + +GST_END_TEST; + +GST_START_TEST (test_audio_pad_frag_reorder) +{ + check_qtmux_pad_fragmented (&srcaudiotemplate, "audio_%u", 1, FALSE); +} + +GST_END_TEST; + + +GST_START_TEST (test_video_pad_frag_reorder_streamable) +{ + check_qtmux_pad_fragmented (&srcvideotemplate, "video_%u", 1, TRUE); +} + +GST_END_TEST; + + +GST_START_TEST (test_audio_pad_frag_reorder_streamable) +{ + check_qtmux_pad_fragmented (&srcaudiotemplate, "audio_%u", 1, TRUE); +} + +GST_END_TEST; + +/* dts-method asc */ + +GST_START_TEST (test_video_pad_asc) +{ + check_qtmux_pad (&srcvideotemplate, "video_%u", 2); +} + +GST_END_TEST; + +GST_START_TEST (test_audio_pad_asc) +{ + check_qtmux_pad (&srcaudiotemplate, "audio_%u", 2); +} + +GST_END_TEST; + + +GST_START_TEST (test_video_pad_frag_asc) +{ + check_qtmux_pad_fragmented (&srcvideotemplate, "video_%u", 2, FALSE); +} + +GST_END_TEST; + +GST_START_TEST (test_audio_pad_frag_asc) +{ + check_qtmux_pad_fragmented (&srcaudiotemplate, "audio_%u", 2, FALSE); +} + +GST_END_TEST; + + +GST_START_TEST (test_video_pad_frag_asc_streamable) +{ + check_qtmux_pad_fragmented (&srcvideotemplate, "video_%u", 2, TRUE); +} + +GST_END_TEST; + + +GST_START_TEST (test_audio_pad_frag_asc_streamable) +{ + check_qtmux_pad_fragmented (&srcaudiotemplate, "audio_%u", 2, TRUE); +} + +GST_END_TEST; + +GST_START_TEST (test_reuse) +{ + GstElement *qtmux = setup_qtmux (&srcvideotemplate, "video_%u"); + GstBuffer *inbuffer; + GstCaps *caps; + GstSegment segment; + + gst_element_set_state (qtmux, GST_STATE_PLAYING); + gst_element_set_state (qtmux, GST_STATE_NULL); + gst_element_set_state (qtmux, GST_STATE_PLAYING); + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + + gst_pad_push_event (mysrcpad, gst_event_new_stream_start ("test")); + + caps = gst_pad_get_pad_template_caps (mysrcpad); + gst_pad_set_caps (mysrcpad, caps); + gst_caps_unref (caps); + + /* ensure segment (format) properly setup */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + inbuffer = gst_buffer_new_and_alloc (1); + fail_unless (inbuffer != NULL); + gst_buffer_memset (inbuffer, 0, 0, 1); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + GST_BUFFER_DURATION (inbuffer) = 40 * GST_MSECOND; + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + + /* send eos to have all written */ + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ()) == TRUE); + + cleanup_qtmux (qtmux, "video_%u"); +} + +GST_END_TEST; + +static GstEncodingContainerProfile * +create_qtmux_profile (const gchar * variant) +{ + GstEncodingContainerProfile *cprof; + GstCaps *caps; + + if (variant == NULL) { + caps = gst_caps_new_empty_simple ("video/quicktime"); + } else { + caps = gst_caps_new_simple ("video/quicktime", + "variant", G_TYPE_STRING, variant, NULL); + } + + cprof = gst_encoding_container_profile_new ("Name", "blah", caps, NULL); + gst_caps_unref (caps); + + caps = gst_caps_new_simple ("audio/x-raw", + "format", G_TYPE_STRING, "S16BE", + "channels", G_TYPE_INT, 2, "rate", G_TYPE_INT, 44100, NULL); + gst_encoding_container_profile_add_profile (cprof, + GST_ENCODING_PROFILE (gst_encoding_audio_profile_new (caps, NULL, NULL, + 1))); + gst_caps_unref (caps); + + return cprof; +} + +GST_START_TEST (test_encodebin_qtmux) +{ + GstEncodingContainerProfile *cprof; + GstElement *enc; + GstPad *pad; + + enc = gst_element_factory_make ("encodebin", NULL); + if (enc == NULL) + return; + + /* Make sure encodebin finds a muxer for a profile with a variant field .. */ + cprof = create_qtmux_profile ("apple"); + g_object_set (enc, "profile", cprof, NULL); + gst_encoding_profile_unref (cprof); + + /* should have created a pad after setting the profile */ + pad = gst_element_get_static_pad (enc, "audio_0"); + fail_unless (pad != NULL); + gst_object_unref (pad); + gst_object_unref (enc); + + /* ... and for a profile without a variant field */ + enc = gst_element_factory_make ("encodebin", NULL); + cprof = create_qtmux_profile (NULL); + g_object_set (enc, "profile", cprof, NULL); + gst_encoding_profile_unref (cprof); + + /* should have created a pad after setting the profile */ + pad = gst_element_get_static_pad (enc, "audio_0"); + fail_unless (pad != NULL); + gst_object_unref (pad); + gst_object_unref (enc); +} + +GST_END_TEST; + +/* Fake mp3 encoder for test */ +typedef GstElement TestMp3Enc; +typedef GstElementClass TestMp3EncClass; + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/mpeg, mpegversion=1, layer=[1,3]") + ); + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw") + ); + +static GType test_mp3_enc_get_type (void); + +G_DEFINE_TYPE (TestMp3Enc, test_mp3_enc, GST_TYPE_ELEMENT); + +static void +test_mp3_enc_class_init (TestMp3EncClass * klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_template)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&src_template)); + + gst_element_class_set_metadata (element_class, "MPEG1 Audio Encoder", + "Codec/Encoder/Audio", "Pretends to encode mp3", "Foo Bar <foo@bar.com>"); +} + +static void +test_mp3_enc_init (TestMp3Enc * mp3enc) +{ + GstPad *pad; + + pad = gst_pad_new_from_static_template (&sink_template, "sink"); + gst_element_add_pad (mp3enc, pad); + + pad = gst_pad_new_from_static_template (&src_template, "src"); + gst_element_add_pad (mp3enc, pad); +} + +static gboolean +plugin_init (GstPlugin * plugin) +{ + return gst_element_register (plugin, "testmp3enc", GST_RANK_NONE, + test_mp3_enc_get_type ()); +} + +static GstEncodingContainerProfile * +create_mp4mux_profile (void) +{ + GstEncodingContainerProfile *cprof; + GstCaps *caps; + + caps = gst_caps_new_simple ("video/quicktime", + "variant", G_TYPE_STRING, "iso", NULL); + + cprof = gst_encoding_container_profile_new ("Name", "blah", caps, NULL); + gst_caps_unref (caps); + + caps = gst_caps_new_simple ("audio/mpeg", "mpegversion", G_TYPE_INT, 1, + "layer", G_TYPE_INT, 3, "channels", G_TYPE_INT, 2, "rate", G_TYPE_INT, + 44100, NULL); + gst_encoding_container_profile_add_profile (cprof, + GST_ENCODING_PROFILE (gst_encoding_audio_profile_new (caps, NULL, NULL, + 1))); + gst_caps_unref (caps); + + return cprof; +} + +GST_START_TEST (test_encodebin_mp4mux) +{ + GstEncodingContainerProfile *cprof; + GstPluginFeature *feature; + GstElement *enc, *mux; + GstPad *pad; + + /* need a fake mp3 encoder because mp4 only accepts encoded formats */ + gst_plugin_register_static (GST_VERSION_MAJOR, GST_VERSION_MINOR, + "fakemp3enc", "fakemp3enc", plugin_init, VERSION, "LGPL", + "gst-plugins-good", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN); + + feature = gst_registry_find_feature (gst_registry_get (), "testmp3enc", + GST_TYPE_ELEMENT_FACTORY); + gst_plugin_feature_set_rank (feature, GST_RANK_PRIMARY + 100); + + enc = gst_element_factory_make ("encodebin", NULL); + if (enc == NULL) + return; + + /* Make sure encodebin finds mp4mux even though qtmux outputs a superset */ + cprof = create_mp4mux_profile (); + g_object_set (enc, "profile", cprof, NULL); + gst_encoding_profile_unref (cprof); + + /* should have created a pad after setting the profile */ + pad = gst_element_get_static_pad (enc, "audio_0"); + fail_unless (pad != NULL); + gst_object_unref (pad); + + mux = gst_bin_get_by_interface (GST_BIN (enc), GST_TYPE_TAG_SETTER); + fail_unless (mux != NULL); + { + GstElementFactory *f = gst_element_get_factory (mux); + + /* make sure we got mp4mux for variant=iso */ + GST_INFO ("muxer: %s", G_OBJECT_TYPE_NAME (mux)); + fail_unless_equals_string (GST_OBJECT_NAME (f), "mp4mux"); + } + gst_object_unref (mux); + gst_object_unref (enc); + + gst_plugin_feature_set_rank (feature, GST_RANK_NONE); + gst_object_unref (feature); +} + +GST_END_TEST; + +static gboolean +extract_tags (const gchar * location, GstTagList ** taglist) +{ + gboolean ret = TRUE; + GstElement *src; + GstBus *bus; + GstElement *pipeline = + gst_parse_launch ("filesrc name=src ! qtdemux ! fakesink", NULL); + + src = gst_bin_get_by_name (GST_BIN (pipeline), "src"); + g_object_set (src, "location", location, NULL); + + bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); + fail_unless (gst_element_set_state (pipeline, GST_STATE_PLAYING) + != GST_STATE_CHANGE_FAILURE); + + if (*taglist == NULL) { + *taglist = gst_tag_list_new_empty (); + } + + while (1) { + GstMessage *msg = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, + GST_MESSAGE_TAG | GST_MESSAGE_ERROR | GST_MESSAGE_EOS); + + if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_EOS) { + gst_message_unref (msg); + break; + } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) { + ret = FALSE; + gst_message_unref (msg); + break; + } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_TAG) { + GstTagList *tags; + + gst_message_parse_tag (msg, &tags); + gst_tag_list_insert (*taglist, tags, GST_TAG_MERGE_REPLACE); + gst_tag_list_unref (tags); + } + gst_message_unref (msg); + } + + gst_object_unref (bus); + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_object_unref (src); + gst_object_unref (pipeline); + return ret; +} + +static void +test_average_bitrate_custom (const gchar * elementname, + GstStaticPadTemplate * tmpl, const gchar * sinkpadname) +{ + gchar *location; + GstElement *qtmux; + GstElement *filesink; + GstBuffer *inbuffer; + GstCaps *caps; + int i; + gint bytes[] = { 16, 22, 12 }; + gint64 durations[] = { GST_SECOND * 3, GST_SECOND * 5, GST_SECOND * 2 }; + gint64 total_bytes = 0; + GstClockTime total_duration = 0; + GstSegment segment; + + location = g_strdup_printf ("%s/%s-%d", g_get_tmp_dir (), "qtmuxtest", + g_random_int ()); + GST_INFO ("Using location %s for bitrate test", location); + qtmux = gst_check_setup_element (elementname); + filesink = gst_element_factory_make ("filesink", NULL); + g_object_set (filesink, "location", location, NULL); + gst_element_link (qtmux, filesink); + mysrcpad = setup_src_pad (qtmux, tmpl, sinkpadname); + fail_unless (mysrcpad != NULL); + gst_pad_set_active (mysrcpad, TRUE); + + + fail_unless (gst_element_set_state (filesink, + GST_STATE_PLAYING) != GST_STATE_CHANGE_FAILURE, + "could not set filesink to playing"); + fail_unless (gst_element_set_state (qtmux, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + gst_pad_push_event (mysrcpad, gst_event_new_stream_start ("test")); + + caps = gst_pad_get_pad_template_caps (mysrcpad); + gst_pad_set_caps (mysrcpad, caps); + gst_caps_unref (caps); + + /* ensure segment (format) properly setup */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + for (i = 0; i < 3; i++) { + inbuffer = gst_buffer_new_and_alloc (bytes[i]); + gst_buffer_memset (inbuffer, 0, 0, bytes[i]); + GST_BUFFER_TIMESTAMP (inbuffer) = total_duration; + GST_BUFFER_DURATION (inbuffer) = (GstClockTime) durations[i]; + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + total_bytes += gst_buffer_get_size (inbuffer); + total_duration += GST_BUFFER_DURATION (inbuffer); + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + } + + /* send eos to have moov written */ + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ()) == TRUE); + + gst_element_set_state (qtmux, GST_STATE_NULL); + gst_element_set_state (filesink, GST_STATE_NULL); + + gst_pad_set_active (mysrcpad, FALSE); + teardown_src_pad (mysrcpad); + gst_object_unref (filesink); + gst_check_teardown_element (qtmux); + + /* check the bitrate tag */ + { + GstTagList *taglist = NULL; + guint bitrate = 0; + guint expected; + + fail_unless (extract_tags (location, &taglist)); + + fail_unless (gst_tag_list_get_uint (taglist, GST_TAG_BITRATE, &bitrate)); + + expected = + (guint) gst_util_uint64_scale_round ((guint64) total_bytes, + (guint64) 8 * GST_SECOND, (guint64) total_duration); + fail_unless (bitrate == expected); + gst_tag_list_unref (taglist); + } + + /* delete file */ + g_unlink (location); + g_free (location); +} + +GST_START_TEST (test_average_bitrate) +{ + test_average_bitrate_custom ("mp4mux", &srcaudioaactemplate, "audio_%u"); + test_average_bitrate_custom ("mp4mux", &srcvideoh264template, "video_%u"); + + test_average_bitrate_custom ("qtmux", &srcaudioaactemplate, "audio_%u"); + test_average_bitrate_custom ("qtmux", &srcvideoh264template, "video_%u"); +} + +GST_END_TEST; + + +static Suite * +qtmux_suite (void) +{ + Suite *s = suite_create ("qtmux"); + TCase *tc_chain = tcase_create ("general"); + + /* avoid glib warnings when setting deprecated dts-method property */ + g_setenv ("G_ENABLE_DIAGNOSTIC", "0", TRUE); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_video_pad_dd); + tcase_add_test (tc_chain, test_audio_pad_dd); + tcase_add_test (tc_chain, test_video_pad_frag_dd); + tcase_add_test (tc_chain, test_audio_pad_frag_dd); + tcase_add_test (tc_chain, test_video_pad_frag_dd_streamable); + tcase_add_test (tc_chain, test_audio_pad_frag_dd_streamable); + + tcase_add_test (tc_chain, test_video_pad_reorder); + tcase_add_test (tc_chain, test_audio_pad_reorder); + tcase_add_test (tc_chain, test_video_pad_frag_reorder); + tcase_add_test (tc_chain, test_audio_pad_frag_reorder); + tcase_add_test (tc_chain, test_video_pad_frag_reorder_streamable); + tcase_add_test (tc_chain, test_audio_pad_frag_reorder_streamable); + + tcase_add_test (tc_chain, test_video_pad_asc); + tcase_add_test (tc_chain, test_audio_pad_asc); + tcase_add_test (tc_chain, test_video_pad_frag_asc); + tcase_add_test (tc_chain, test_audio_pad_frag_asc); + tcase_add_test (tc_chain, test_video_pad_frag_asc_streamable); + tcase_add_test (tc_chain, test_audio_pad_frag_asc_streamable); + + tcase_add_test (tc_chain, test_average_bitrate); + + tcase_add_test (tc_chain, test_reuse); + tcase_add_test (tc_chain, test_encodebin_qtmux); + tcase_add_test (tc_chain, test_encodebin_mp4mux); + + return s; +} + +GST_CHECK_MAIN (qtmux) diff --git a/tests/check/elements/rganalysis.c b/tests/check/elements/rganalysis.c new file mode 100644 index 0000000..1f6f6ee --- /dev/null +++ b/tests/check/elements/rganalysis.c @@ -0,0 +1,1994 @@ +/* GStreamer ReplayGain analysis + * + * Copyright (C) 2006 Rene Stadler <mail@renestadler.de> + * + * rganalysis.c: Unit test for the rganalysis element + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +/* Some things to note about the RMS window length of the analysis algorithm and + * thus the implementation used in the element: Processing divides input data + * into 50ms windows at some point. Some details about this that normally do + * not matter: + * + * 1. At the end of a stream, the remainder of data that did not fill up the + * last 50ms window is simply discarded. + * + * 2. If the sample rate changes during a stream, the currently running window + * is discarded and the equal loudness filter gets reset as if a new stream + * started. + * + * 3. For the album gain, it is not entirely correct to think of obtaining it + * like "as if all the tracks are analyzed as one track". There isn't a + * separate window being tracked for album processing, so at stream (track) + * end, the remaining unfilled window does not contribute to the album gain + * either. + * + * 4. If a waveform with a result gain G is concatenated to itself and the + * result processed as a track, the gain can be different from G if and only + * if the duration of the original waveform is not an integer multiple of + * 50ms. If the original waveform gets processed as a single track and then + * the same data again as a subsequent track, the album result gain will + * always match G (this is implied by 3.). + * + * 5. A stream shorter than 50ms cannot be analyzed. At 8000 and 48000 Hz, + * this corresponds to 400 resp. 2400 frames. If a stream is shorter than + * 50ms, the element will not generate tags at EOS (only if an album + * finished, but only album tags are generated then). This is not an + * erroneous condition, the element should behave normally. + * + * The limitations outlined in 1.-4. do not apply to the peak values. Every + * single sample is accounted for when looking for the peak. Thus the album + * peak is guaranteed to be the maximum value of all track peaks. + * + * In normal day-to-day use, these little facts are unlikely to be relevant, but + * they have to be kept in mind for writing the tests here. + */ + +#include <gst/check/gstcheck.h> +#include <gst/audio/audio.h> + +/* 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; +static GstBus *mybus; + +/* Mapping from supported sample rates to the correct result gain for the + * following test waveform: 20 * 512 samples with a quarter-full amplitude of + * toggling sign, changing every 48 samples and starting with the positive + * value. + * + * Even if we would generate a wave describing a signal with the same frequency + * at each sampling rate, the results would vary (slightly). Hence the simple + * generation method, since we cannot use a constant value as expected result + * anyways. For all sample rates, changing the sign every 48 frames gives a + * sane frequency. Buffers containing data that forms such a waveform is + * created using the test_buffer_square_{float,int16}_{mono,stereo} functions + * below. + * + * The results have been checked against what the metaflac and wavegain programs + * generate for such a stream. If you want to verify these, be sure that the + * metaflac program does not produce incorrect results in your environment: I + * found a strange bug in the (defacto) reference code for the analysis that + * sometimes leads to incorrect RMS window lengths. */ + +struct rate_test +{ + guint sample_rate; + gdouble gain; +}; + +static const struct rate_test supported_rates[] = { + {8000, -0.91}, + {11025, -2.80}, + {12000, -3.13}, + {16000, -4.26}, + {22050, -5.64}, + {24000, -5.87}, + {32000, -6.03}, + {44100, -6.20}, + {48000, -6.14} +}; + +/* Lookup the correct gain adjustment result in above array. */ + +static gdouble +get_expected_gain (guint sample_rate) +{ + gint i; + + for (i = G_N_ELEMENTS (supported_rates); i--;) + if (supported_rates[i].sample_rate == sample_rate) + return supported_rates[i].gain; + g_return_val_if_reached (0.0); +} + +#define SILENCE_GAIN 64.82 + +#define REPLAY_GAIN_CAPS \ + "channels = (int) { 1, 2 }, " \ + "rate = (int) { 8000, 11025, 12000, 16000, 22050, " \ + "24000, 32000, 44100, 48000 }" + +#define RG_ANALYSIS_CAPS_TEMPLATE_STRING \ + "audio/x-raw, " \ + "format = (string) "GST_AUDIO_NE (F32) ", " \ + "layout = (string) interleaved, " \ + REPLAY_GAIN_CAPS \ + "; " \ + "audio/x-raw, " \ + "format = (string) "GST_AUDIO_NE (S16) ", " \ + "layout = (string) interleaved, " \ + REPLAY_GAIN_CAPS + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (RG_ANALYSIS_CAPS_TEMPLATE_STRING) + ); +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (RG_ANALYSIS_CAPS_TEMPLATE_STRING) + ); + +static gboolean +mysink_event_func (GstPad * pad, GstObject * parent, GstEvent * event) +{ + GST_LOG_OBJECT (pad, "%s event: %" GST_PTR_FORMAT, + GST_EVENT_TYPE_NAME (event), event); + + /* a sink would post tag events as messages, so do the same here, + * esp. since we're polling on the bus waiting for TAG messages.. */ + if (GST_EVENT_TYPE (event) == GST_EVENT_TAG) { + GstTagList *taglist; + + gst_event_parse_tag (event, &taglist); + + gst_bus_post (mybus, gst_message_new_tag (GST_OBJECT (mysinkpad), + gst_tag_list_copy (taglist))); + } + + gst_event_unref (event); + return TRUE; +} + +static GstElement * +setup_rganalysis (void) +{ + GstElement *analysis; + GstBus *bus; + + GST_DEBUG ("setup_rganalysis"); + analysis = gst_check_setup_element ("rganalysis"); + mysrcpad = gst_check_setup_src_pad (analysis, &srctemplate); + mysinkpad = gst_check_setup_sink_pad (analysis, &sinktemplate); + gst_pad_set_event_function (mysinkpad, mysink_event_func); + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + + bus = gst_bus_new (); + gst_element_set_bus (analysis, bus); + + mybus = bus; /* keep ref */ + + return analysis; +} + +static void +cleanup_rganalysis (GstElement * element) +{ + GST_DEBUG ("cleanup_rganalysis"); + + g_list_foreach (buffers, (GFunc) gst_mini_object_unref, NULL); + g_list_free (buffers); + buffers = NULL; + + gst_object_unref (mybus); + mybus = NULL; + + /* The bus owns references to the element: */ + gst_element_set_bus (element, NULL); + + gst_pad_set_active (mysrcpad, FALSE); + gst_pad_set_active (mysinkpad, FALSE); + gst_check_teardown_src_pad (element); + gst_check_teardown_sink_pad (element); + gst_check_teardown_element (element); +} + +static void +set_playing_state (GstElement * element) +{ + fail_unless (gst_element_set_state (element, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "Could not set state to PLAYING"); +} + +static void +send_flush_events (GstElement * element) +{ + gboolean res; + GstPad *pad; + + pad = mysrcpad; + res = gst_pad_push_event (pad, gst_event_new_flush_start ()); + fail_unless (res, "flush-start even not handledt"); + res = gst_pad_push_event (pad, gst_event_new_flush_stop (TRUE)); + fail_unless (res, "flush-stop event not handled"); +} + +static void +send_stream_start_event (GstElement * element) +{ + gboolean res; + GstPad *pad; + + pad = mysrcpad; + res = gst_pad_push_event (pad, gst_event_new_stream_start ("test")); + fail_unless (res, "STREAM_START event not handled"); +} + +static void +send_caps_event (const gchar * format, gint sample_rate, gint channels) +{ + GstCaps *caps; + + caps = gst_caps_new_simple ("audio/x-raw", + "format", G_TYPE_STRING, format, + "rate", G_TYPE_INT, sample_rate, "channels", G_TYPE_INT, channels, + "layout", G_TYPE_STRING, "interleaved", NULL); + if (channels == 2) { + gst_caps_set_simple (caps, + "channel-mask", GST_TYPE_BITMASK, + G_GUINT64_CONSTANT (0x0000000000000003), NULL); + } + gst_pad_set_caps (mysrcpad, caps); + gst_caps_unref (caps); +} + +static void +send_segment_event (GstElement * element) +{ + GstSegment segment; + gboolean res; + GstPad *pad; + + pad = mysrcpad; + gst_segment_init (&segment, GST_FORMAT_TIME); + res = gst_pad_push_event (pad, gst_event_new_segment (&segment)); + fail_unless (res, "SEGMENT event not handled"); +} + +static void +send_eos_event (GstElement * element) +{ + GstBus *bus = gst_element_get_bus (element); + GstPad *pad = mysrcpad; + gboolean res; + + res = gst_pad_push_event (pad, gst_event_new_eos ()); + fail_unless (res, "EOS event not handled"); + + /* There is no sink element, so _we_ post the EOS message on the bus here. Of + * course we generate any EOS ourselves, but this allows us to poll for the + * EOS message in poll_eos if we expect the element to _not_ generate a TAG + * message. That's better than waiting for a timeout to lapse. */ + fail_unless (gst_bus_post (bus, gst_message_new_eos (NULL))); + + gst_object_unref (bus); +} + +static void +send_tag_event (GstElement * element, GstTagList * tag_list) +{ + GstPad *pad = mysrcpad; + GstEvent *event = gst_event_new_tag (tag_list); + + fail_unless (gst_pad_push_event (pad, event), + "Cannot send TAG event: Not handled."); +} + +static void +poll_eos (GstElement * element) +{ + GstBus *bus = gst_element_get_bus (element); + GstMessage *message; + + message = gst_bus_poll (bus, GST_MESSAGE_EOS | GST_MESSAGE_TAG, GST_SECOND); + fail_unless (message != NULL, "Could not poll for EOS message: Timed out"); + fail_unless (message->type == GST_MESSAGE_EOS, + "Could not poll for eos message: got message of type %s instead", + gst_message_type_get_name (message->type)); + + gst_message_unref (message); + gst_object_unref (bus); +} + +static GstTagList * +poll_tags_only (GstElement * element) +{ + GstBus *bus = gst_element_get_bus (element); + GstTagList *tag_list; + GstMessage *message; + + message = gst_bus_poll (bus, GST_MESSAGE_TAG, GST_SECOND); + fail_unless (message != NULL, "Could not poll for TAG message: Timed out"); + + gst_message_parse_tag (message, &tag_list); + gst_message_unref (message); + gst_object_unref (bus); + + return tag_list; +} + +/* This also polls for EOS since the TAG message comes right before the end of + * streams. */ + +static GstTagList * +poll_tags_followed_by_eos (GstElement * element) +{ + GstTagList *tag_list = poll_tags_only (element); + + poll_eos (element); + + return tag_list; +} + +#define MATCH_PEAK(p1, p2) ((p1 < p2 + 1e-6) && (p2 < p1 + 1e-6)) +#define MATCH_GAIN(g1, g2) ((g1 < g2 + 1e-13) && (g2 < g1 + 1e-13)) + +static void +fail_unless_track_gain (const GstTagList * tag_list, gdouble gain) +{ + gdouble result; + + fail_unless (gst_tag_list_get_double (tag_list, GST_TAG_TRACK_GAIN, &result), + "Tag list contains no track gain value"); + fail_unless (MATCH_GAIN (gain, result), + "Track gain %+.2f does not match, expected %+.2f", result, gain); +} + +static void +fail_unless_track_peak (const GstTagList * tag_list, gdouble peak) +{ + gdouble result; + + fail_unless (gst_tag_list_get_double (tag_list, GST_TAG_TRACK_PEAK, &result), + "Tag list contains no track peak value"); + fail_unless (MATCH_PEAK (peak, result), + "Track peak %f does not match, expected %f", result, peak); +} + +static void +fail_unless_album_gain (const GstTagList * tag_list, gdouble gain) +{ + gdouble result; + + fail_unless (gst_tag_list_get_double (tag_list, GST_TAG_ALBUM_GAIN, &result), + "Tag list contains no album gain value"); + fail_unless (MATCH_GAIN (result, gain), + "Album gain %+.2f does not match, expected %+.2f", result, gain); +} + +static void +fail_unless_album_peak (const GstTagList * tag_list, gdouble peak) +{ + gdouble result; + + fail_unless (gst_tag_list_get_double (tag_list, GST_TAG_ALBUM_PEAK, &result), + "Tag list contains no album peak value"); + fail_unless (MATCH_PEAK (peak, result), + "Album peak %f does not match, expected %f", result, peak); +} + +static void +fail_if_track_tags (const GstTagList * tag_list) +{ + gdouble result; + + fail_if (gst_tag_list_get_double (tag_list, GST_TAG_TRACK_GAIN, &result), + "Tag list contains track gain value (but should not)"); + fail_if (gst_tag_list_get_double (tag_list, GST_TAG_TRACK_PEAK, &result), + "Tag list contains track peak value (but should not)"); +} + +static void +fail_if_album_tags (const GstTagList * tag_list) +{ + gdouble result; + + fail_if (gst_tag_list_get_double (tag_list, GST_TAG_ALBUM_GAIN, &result), + "Tag list contains album gain value (but should not)"); + fail_if (gst_tag_list_get_double (tag_list, GST_TAG_ALBUM_PEAK, &result), + "Tag list contains album peak value (but should not)"); +} + +static void +fail_unless_num_tracks (GstElement * element, guint num_tracks) +{ + guint current; + + g_object_get (element, "num-tracks", ¤t, NULL); + fail_unless (current == num_tracks, + "num-tracks property has incorrect value %u, expected %u", + current, num_tracks); +} + +/* Functions that create buffers with constant sample values, for peak + * tests. */ + +static GstBuffer * +test_buffer_const_float_mono (gint sample_rate, gsize n_frames, gfloat value) +{ + GstBuffer *buf = gst_buffer_new_and_alloc (n_frames * sizeof (gfloat)); + GstMapInfo map; + gfloat *data; + gint i; + + gst_buffer_map (buf, &map, GST_MAP_WRITE); + data = (gfloat *) map.data; + for (i = n_frames; i--;) + *data++ = value; + gst_buffer_unmap (buf, &map); + + ASSERT_BUFFER_REFCOUNT (buf, "buf", 1); + + return buf; +} + +static GstBuffer * +test_buffer_const_float_stereo (gint sample_rate, gsize n_frames, + gfloat value_l, gfloat value_r) +{ + GstBuffer *buf = gst_buffer_new_and_alloc (n_frames * sizeof (gfloat) * 2); + GstMapInfo map; + gfloat *data; + gint i; + + gst_buffer_map (buf, &map, GST_MAP_WRITE); + data = (gfloat *) map.data; + for (i = n_frames; i--;) { + *data++ = value_l; + *data++ = value_r; + } + gst_buffer_unmap (buf, &map); + + ASSERT_BUFFER_REFCOUNT (buf, "buf", 1); + + return buf; +} + +static GstBuffer * +test_buffer_const_int16_mono (gint sample_rate, gint depth, gsize n_frames, + gint16 value) +{ + GstBuffer *buf = gst_buffer_new_and_alloc (n_frames * sizeof (gint16)); + gint16 *data; + GstMapInfo map; + gint i; + + gst_buffer_map (buf, &map, GST_MAP_WRITE); + data = (gint16 *) map.data; + for (i = n_frames; i--;) + *data++ = value; + gst_buffer_unmap (buf, &map); + + ASSERT_BUFFER_REFCOUNT (buf, "buf", 1); + + return buf; +} + +static GstBuffer * +test_buffer_const_int16_stereo (gint sample_rate, gint depth, gsize n_frames, + gint16 value_l, gint16 value_r) +{ + GstBuffer *buf = gst_buffer_new_and_alloc (n_frames * sizeof (gint16) * 2); + gint16 *data; + GstMapInfo map; + gint i; + + gst_buffer_map (buf, &map, GST_MAP_WRITE); + data = (gint16 *) map.data; + for (i = n_frames; i--;) { + *data++ = value_l; + *data++ = value_r; + } + gst_buffer_unmap (buf, &map); + + ASSERT_BUFFER_REFCOUNT (buf, "buf", 1); + + return buf; +} + +/* Functions that create data buffers containing square signal + * waveforms. */ + +static GstBuffer * +test_buffer_square_float_mono (gint * accumulator, gint sample_rate, + gsize n_frames, gfloat value) +{ + GstBuffer *buf = gst_buffer_new_and_alloc (n_frames * sizeof (gfloat)); + gfloat *data; + GstMapInfo map; + gint i; + + gst_buffer_map (buf, &map, GST_MAP_WRITE); + data = (gfloat *) map.data; + for (i = n_frames; i--;) { + *accumulator += 1; + *accumulator %= 96; + + if (*accumulator < 48) + *data++ = value; + else + *data++ = -value; + } + gst_buffer_unmap (buf, &map); + + ASSERT_BUFFER_REFCOUNT (buf, "buf", 1); + + return buf; +} + +static GstBuffer * +test_buffer_square_float_stereo (gint * accumulator, gint sample_rate, + gsize n_frames, gfloat value_l, gfloat value_r) +{ + GstBuffer *buf = gst_buffer_new_and_alloc (n_frames * sizeof (gfloat) * 2); + gfloat *data; + GstMapInfo map; + gint i; + + gst_buffer_map (buf, &map, GST_MAP_WRITE); + data = (gfloat *) map.data; + for (i = n_frames; i--;) { + *accumulator += 1; + *accumulator %= 96; + + if (*accumulator < 48) { + *data++ = value_l; + *data++ = value_r; + } else { + *data++ = -value_l; + *data++ = -value_r; + } + } + gst_buffer_unmap (buf, &map); + + ASSERT_BUFFER_REFCOUNT (buf, "buf", 1); + + return buf; +} + +static GstBuffer * +test_buffer_square_int16_mono (gint * accumulator, gint sample_rate, + gint depth, gsize n_frames, gint16 value) +{ + GstBuffer *buf = gst_buffer_new_and_alloc (n_frames * sizeof (gint16)); + gint16 *data; + GstMapInfo map; + gint i; + + gst_buffer_map (buf, &map, GST_MAP_WRITE); + data = (gint16 *) map.data; + for (i = n_frames; i--;) { + *accumulator += 1; + *accumulator %= 96; + + if (*accumulator < 48) + *data++ = value; + else + *data++ = -MAX (value, -32767); + } + gst_buffer_unmap (buf, &map); + + ASSERT_BUFFER_REFCOUNT (buf, "buf", 1); + + return buf; +} + +static GstBuffer * +test_buffer_square_int16_stereo (gint * accumulator, gint sample_rate, + gint depth, gsize n_frames, gint16 value_l, gint16 value_r) +{ + GstBuffer *buf = gst_buffer_new_and_alloc (n_frames * sizeof (gint16) * 2); + gint16 *data; + GstMapInfo map; + gint i; + + gst_buffer_map (buf, &map, GST_MAP_WRITE); + data = (gint16 *) map.data; + for (i = n_frames; i--;) { + *accumulator += 1; + *accumulator %= 96; + + if (*accumulator < 48) { + *data++ = value_l; + *data++ = value_r; + } else { + *data++ = -MAX (value_l, -32767); + *data++ = -MAX (value_r, -32767); + } + } + gst_buffer_unmap (buf, &map); + + ASSERT_BUFFER_REFCOUNT (buf, "buf", 1); + + return buf; +} + +static void +push_buffer (GstBuffer * buf) +{ + /* gst_pad_push steals a reference. */ + fail_unless (gst_pad_push (mysrcpad, buf) == GST_FLOW_OK); + ASSERT_BUFFER_REFCOUNT (buf, "buf", 1); +} + +/*** Start of the tests. ***/ + +/* This test looks redundant, but early versions of the element + * crashed when doing, well, nothing: */ + +GST_START_TEST (test_no_buffer) +{ + GstElement *element = setup_rganalysis (); + + set_playing_state (element); + send_eos_event (element); + poll_eos (element); + + cleanup_rganalysis (element); +} + +GST_END_TEST; + +GST_START_TEST (test_no_buffer_album_1) +{ + GstElement *element = setup_rganalysis (); + + set_playing_state (element); + + /* Single track: */ + send_stream_start_event (element); + send_segment_event (element); + send_eos_event (element); + poll_eos (element); + + /* First album: */ + g_object_set (element, "num-tracks", 3, NULL); + + send_flush_events (element); + send_segment_event (element); + send_eos_event (element); + poll_eos (element); + fail_unless_num_tracks (element, 2); + + send_flush_events (element); + send_segment_event (element); + send_eos_event (element); + poll_eos (element); + fail_unless_num_tracks (element, 1); + + send_flush_events (element); + send_segment_event (element); + send_eos_event (element); + poll_eos (element); + fail_unless_num_tracks (element, 0); + + /* Second album: */ + g_object_set (element, "num-tracks", 2, NULL); + + send_flush_events (element); + send_segment_event (element); + send_eos_event (element); + poll_eos (element); + fail_unless_num_tracks (element, 1); + + send_flush_events (element); + send_segment_event (element); + send_eos_event (element); + poll_eos (element); + fail_unless_num_tracks (element, 0); + + /* Single track: */ + send_flush_events (element); + send_segment_event (element); + send_eos_event (element); + poll_eos (element); + fail_unless_num_tracks (element, 0); + + cleanup_rganalysis (element); +} + +GST_END_TEST; + +GST_START_TEST (test_no_buffer_album_2) +{ + GstElement *element = setup_rganalysis (); + GstTagList *tag_list; + gint accumulator = 0; + gint i; + + g_object_set (element, "num-tracks", 3, NULL); + set_playing_state (element); + + /* No buffer for the first track. */ + send_stream_start_event (element); + send_segment_event (element); + send_eos_event (element); + /* No tags should be posted, there was nothing to analyze: */ + poll_eos (element); + fail_unless_num_tracks (element, 2); + + /* A test waveform with known gain result as second track: */ + + send_flush_events (element); + send_caps_event (GST_AUDIO_NE (F32), 44100, 1); + send_segment_event (element); + for (i = 20; i--;) + push_buffer (test_buffer_square_float_mono (&accumulator, 44100, 512, + 0.25)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.25); + fail_unless_track_gain (tag_list, -6.20); + /* Album is not finished yet: */ + fail_if_album_tags (tag_list); + gst_tag_list_unref (tag_list); + fail_unless_num_tracks (element, 1); + + /* No buffer for the last track. */ + + send_flush_events (element); + send_segment_event (element); + send_eos_event (element); + + tag_list = poll_tags_followed_by_eos (element); + fail_unless_album_peak (tag_list, 0.25); + fail_unless_album_gain (tag_list, -6.20); + /* No track tags should be posted, as there was no data for it: */ + fail_if_track_tags (tag_list); + gst_tag_list_unref (tag_list); + fail_unless_num_tracks (element, 0); + + cleanup_rganalysis (element); +} + +GST_END_TEST; + +GST_START_TEST (test_empty_buffers) +{ + GstElement *element = setup_rganalysis (); + + set_playing_state (element); + + /* Single track: */ + send_stream_start_event (element); + send_caps_event (GST_AUDIO_NE (F32), 44100, 2); + send_segment_event (element); + push_buffer (test_buffer_const_float_stereo (44100, 0, 0.0, 0.0)); + send_eos_event (element); + poll_eos (element); + + /* First album: */ + g_object_set (element, "num-tracks", 2, NULL); + + send_flush_events (element); + send_segment_event (element); + push_buffer (test_buffer_const_float_stereo (44100, 0, 0.0, 0.0)); + send_eos_event (element); + poll_eos (element); + fail_unless_num_tracks (element, 1); + + send_flush_events (element); + send_segment_event (element); + push_buffer (test_buffer_const_float_stereo (44100, 0, 0.0, 0.0)); + send_eos_event (element); + poll_eos (element); + fail_unless_num_tracks (element, 0); + + /* Second album, with a single track: */ + g_object_set (element, "num-tracks", 1, NULL); + send_flush_events (element); + send_segment_event (element); + push_buffer (test_buffer_const_float_stereo (44100, 0, 0.0, 0.0)); + send_eos_event (element); + poll_eos (element); + fail_unless_num_tracks (element, 0); + + /* Single track: */ + send_flush_events (element); + send_segment_event (element); + push_buffer (test_buffer_const_float_stereo (44100, 0, 0.0, 0.0)); + send_eos_event (element); + poll_eos (element); + + cleanup_rganalysis (element); +} + +GST_END_TEST; + +/* Tests for correctness of the peak values. */ + +/* Float peak test. For stereo, one channel has the constant value of -1.369, + * the other one 0.0. This tests many things: The result peak value should + * occur on any channel. The peak is of course the absolute amplitude, so 1.369 + * should be the result. This will also detect if the code uses the absolute + * value during the comparison. If it is buggy it will return 0.0 since 0.0 > + * -1.369. Furthermore, this makes sure that there is no problem with headroom + * (exceeding 0dBFS). In the wild you get float samples > 1.0 from stuff like + * vorbis. */ + +GST_START_TEST (test_peak_float) +{ + GstElement *element = setup_rganalysis (); + GstTagList *tag_list; + + set_playing_state (element); + send_stream_start_event (element); + send_caps_event (GST_AUDIO_NE (F32), 8000, 2); + send_segment_event (element); + push_buffer (test_buffer_const_float_stereo (8000, 512, -1.369, 0.0)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 1.369); + gst_tag_list_unref (tag_list); + + /* Swapped channels. */ + send_flush_events (element); + send_segment_event (element); + push_buffer (test_buffer_const_float_stereo (8000, 512, 0.0, -1.369)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 1.369); + gst_tag_list_unref (tag_list); + + /* Mono. */ + send_flush_events (element); + send_caps_event (GST_AUDIO_NE (F32), 8000, 1); + send_segment_event (element); + push_buffer (test_buffer_const_float_mono (8000, 512, -1.369)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 1.369); + gst_tag_list_unref (tag_list); + + cleanup_rganalysis (element); +} + +GST_END_TEST; + +GST_START_TEST (test_peak_int16_16) +{ + GstElement *element = setup_rganalysis (); + GstTagList *tag_list; + + set_playing_state (element); + + /* Half amplitude. */ + send_stream_start_event (element); + send_caps_event (GST_AUDIO_NE (S16), 8000, 2); + send_segment_event (element); + push_buffer (test_buffer_const_int16_stereo (8000, 16, 512, 1 << 14, 0)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.5); + gst_tag_list_unref (tag_list); + + /* Swapped channels. */ + send_flush_events (element); + send_caps_event (GST_AUDIO_NE (S16), 8000, 2); + send_segment_event (element); + push_buffer (test_buffer_const_int16_stereo (8000, 16, 512, 0, 1 << 14)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.5); + gst_tag_list_unref (tag_list); + + /* Mono. */ + send_flush_events (element); + send_caps_event (GST_AUDIO_NE (S16), 8000, 1); + send_segment_event (element); + push_buffer (test_buffer_const_int16_mono (8000, 16, 512, 1 << 14)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.5); + gst_tag_list_unref (tag_list); + + /* Half amplitude, negative variant. */ + send_flush_events (element); + send_caps_event (GST_AUDIO_NE (S16), 8000, 2); + send_segment_event (element); + push_buffer (test_buffer_const_int16_stereo (8000, 16, 512, -1 << 14, 0)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.5); + gst_tag_list_unref (tag_list); + + /* Swapped channels. */ + send_flush_events (element); + send_caps_event (GST_AUDIO_NE (S16), 8000, 2); + send_segment_event (element); + push_buffer (test_buffer_const_int16_stereo (8000, 16, 512, 0, -1 << 14)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.5); + gst_tag_list_unref (tag_list); + + /* Mono. */ + send_flush_events (element); + send_caps_event (GST_AUDIO_NE (S16), 8000, 1); + send_segment_event (element); + push_buffer (test_buffer_const_int16_mono (8000, 16, 512, -1 << 14)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.5); + gst_tag_list_unref (tag_list); + + + /* Now check for correct normalization of the peak value: Sample + * values of this format range from -32768 to 32767. So for the + * highest positive amplitude we do not reach 1.0, only for + * -32768! */ + + send_flush_events (element); + send_caps_event (GST_AUDIO_NE (S16), 8000, 2); + send_segment_event (element); + push_buffer (test_buffer_const_int16_stereo (8000, 16, 512, 32767, 0)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 32767. / 32768.); + gst_tag_list_unref (tag_list); + + /* Swapped channels. */ + send_flush_events (element); + send_caps_event (GST_AUDIO_NE (S16), 8000, 2); + send_segment_event (element); + push_buffer (test_buffer_const_int16_stereo (8000, 16, 512, 0, 32767)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 32767. / 32768.); + gst_tag_list_unref (tag_list); + + /* Mono. */ + send_flush_events (element); + send_caps_event (GST_AUDIO_NE (S16), 8000, 1); + send_segment_event (element); + push_buffer (test_buffer_const_int16_mono (8000, 16, 512, 32767)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 32767. / 32768.); + gst_tag_list_unref (tag_list); + + + /* Negative variant, reaching 1.0. */ + send_flush_events (element); + send_caps_event (GST_AUDIO_NE (S16), 8000, 2); + send_segment_event (element); + push_buffer (test_buffer_const_int16_stereo (8000, 16, 512, -32768, 0)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 1.0); + gst_tag_list_unref (tag_list); + + /* Swapped channels. */ + send_flush_events (element); + send_caps_event (GST_AUDIO_NE (S16), 8000, 2); + send_segment_event (element); + push_buffer (test_buffer_const_int16_stereo (8000, 16, 512, 0, -32768)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 1.0); + gst_tag_list_unref (tag_list); + + /* Mono. */ + send_flush_events (element); + send_caps_event (GST_AUDIO_NE (S16), 8000, 1); + send_segment_event (element); + push_buffer (test_buffer_const_int16_mono (8000, 16, 512, -32768)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 1.0); + gst_tag_list_unref (tag_list); + + cleanup_rganalysis (element); +} + +GST_END_TEST; + +GST_START_TEST (test_peak_album) +{ + GstElement *element = setup_rganalysis (); + GstTagList *tag_list; + + g_object_set (element, "num-tracks", 2, NULL); + set_playing_state (element); + + send_stream_start_event (element); + send_caps_event (GST_AUDIO_NE (F32), 8000, 2); + send_segment_event (element); + push_buffer (test_buffer_const_float_stereo (8000, 1024, 1.0, 0.0)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 1.0); + fail_if_album_tags (tag_list); + gst_tag_list_unref (tag_list); + fail_unless_num_tracks (element, 1); + + send_flush_events (element); + send_segment_event (element); + push_buffer (test_buffer_const_float_stereo (8000, 1024, 0.0, 0.5)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.5); + fail_unless_album_peak (tag_list, 1.0); + gst_tag_list_unref (tag_list); + fail_unless_num_tracks (element, 0); + + /* Try a second album: */ + g_object_set (element, "num-tracks", 3, NULL); + + send_flush_events (element); + send_segment_event (element); + push_buffer (test_buffer_const_float_stereo (8000, 1024, 0.4, 0.4)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.4); + fail_if_album_tags (tag_list); + gst_tag_list_unref (tag_list); + fail_unless_num_tracks (element, 2); + + send_flush_events (element); + send_segment_event (element); + push_buffer (test_buffer_const_float_stereo (8000, 1024, 0.45, 0.45)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.45); + fail_if_album_tags (tag_list); + gst_tag_list_unref (tag_list); + fail_unless_num_tracks (element, 1); + + send_flush_events (element); + send_segment_event (element); + push_buffer (test_buffer_const_float_stereo (8000, 1024, 0.2, 0.2)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.2); + fail_unless_album_peak (tag_list, 0.45); + gst_tag_list_unref (tag_list); + fail_unless_num_tracks (element, 0); + + /* And now a single track, not in album mode (num-tracks is 0 + * now): */ + send_flush_events (element); + send_segment_event (element); + push_buffer (test_buffer_const_float_stereo (8000, 1024, 0.1, 0.1)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.1); + fail_if_album_tags (tag_list); + gst_tag_list_unref (tag_list); + + cleanup_rganalysis (element); +} + +GST_END_TEST; + +/* Switching from track to album mode. */ + +GST_START_TEST (test_peak_track_album) +{ + GstElement *element = setup_rganalysis (); + GstTagList *tag_list; + + set_playing_state (element); + + send_stream_start_event (element); + send_caps_event (GST_AUDIO_NE (F32), 8000, 1); + send_segment_event (element); + push_buffer (test_buffer_const_float_mono (8000, 1024, 1.0)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 1.0); + fail_if_album_tags (tag_list); + gst_tag_list_unref (tag_list); + + g_object_set (element, "num-tracks", 1, NULL); + send_flush_events (element); + send_segment_event (element); + push_buffer (test_buffer_const_float_mono (8000, 1024, 0.5)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.5); + fail_unless_album_peak (tag_list, 0.5); + gst_tag_list_unref (tag_list); + fail_unless_num_tracks (element, 0); + + cleanup_rganalysis (element); +} + +GST_END_TEST; + +/* Disabling album processing before the end of the album. Probably a rare edge + * case and applications should not rely on this to work. They need to send the + * element to the READY state to clear up after an aborted album anyway since + * they might need to process another album afterwards. */ + +GST_START_TEST (test_peak_album_abort_to_track) +{ + GstElement *element = setup_rganalysis (); + GstTagList *tag_list; + + g_object_set (element, "num-tracks", 2, NULL); + set_playing_state (element); + + send_stream_start_event (element); + send_caps_event (GST_AUDIO_NE (F32), 8000, 2); + send_segment_event (element); + push_buffer (test_buffer_const_float_stereo (8000, 1024, 1.0, 0.0)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 1.0); + fail_if_album_tags (tag_list); + gst_tag_list_unref (tag_list); + fail_unless_num_tracks (element, 1); + + g_object_set (element, "num-tracks", 0, NULL); + + send_flush_events (element); + send_segment_event (element); + push_buffer (test_buffer_const_float_stereo (8000, 1024, 0.0, 0.5)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.5); + fail_if_album_tags (tag_list); + gst_tag_list_unref (tag_list); + + cleanup_rganalysis (element); +} + +GST_END_TEST; + +GST_START_TEST (test_gain_album) +{ + GstElement *element = setup_rganalysis (); + GstTagList *tag_list; + gint accumulator; + gint i; + + g_object_set (element, "num-tracks", 3, NULL); + set_playing_state (element); + + /* The three tracks are constructed such that if any of these is in fact + * ignored for the album gain, the album gain will differ. */ + send_stream_start_event (element); + send_caps_event (GST_AUDIO_NE (F32), 44100, 2); + send_segment_event (element); + accumulator = 0; + for (i = 8; i--;) + push_buffer (test_buffer_square_float_stereo (&accumulator, 44100, 512, + 0.75, 0.75)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.75); + fail_unless_track_gain (tag_list, -15.70); + fail_if_album_tags (tag_list); + gst_tag_list_unref (tag_list); + + send_flush_events (element); + send_segment_event (element); + accumulator = 0; + for (i = 12; i--;) + push_buffer (test_buffer_square_float_stereo (&accumulator, 44100, 512, + 0.5, 0.5)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.5); + fail_unless_track_gain (tag_list, -12.22); + fail_if_album_tags (tag_list); + gst_tag_list_unref (tag_list); + + send_flush_events (element); + send_segment_event (element); + accumulator = 0; + for (i = 180; i--;) + push_buffer (test_buffer_square_float_stereo (&accumulator, 44100, 512, + 0.25, 0.25)); + send_eos_event (element); + + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.25); + fail_unless_track_gain (tag_list, -6.20); + fail_unless_album_peak (tag_list, 0.75); + /* Strangely, wavegain reports -12.17 for the album, but the fixed + * metaflac agrees to us. Could be a 32767 vs. 32768 issue. */ + fail_unless_album_gain (tag_list, -12.18); + gst_tag_list_unref (tag_list); + + cleanup_rganalysis (element); +} + +GST_END_TEST; + +/* Checks ensuring that the "forced" property works as advertised. */ + +GST_START_TEST (test_forced) +{ + GstElement *element = setup_rganalysis (); + GstTagList *tag_list; + gint accumulator = 0; + gint i; + + g_object_set (element, "forced", FALSE, NULL); + set_playing_state (element); + + send_stream_start_event (element); + send_caps_event (GST_AUDIO_NE (F32), 44100, 2); + send_segment_event (element); + tag_list = gst_tag_list_new_empty (); + /* Provided values are totally arbitrary. */ + gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, + GST_TAG_TRACK_PEAK, 1.0, GST_TAG_TRACK_GAIN, 2.21, NULL); + send_tag_event (element, tag_list); + + for (i = 20; i--;) + push_buffer (test_buffer_const_float_stereo (44100, 512, 0.5, 0.5)); + send_eos_event (element); + + /* This fails if a tag message is generated: */ + /* Same values as above */ + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_gain (tag_list, 2.21); + fail_unless_track_peak (tag_list, 1.0); + gst_tag_list_unref (tag_list); + + /* Now back to a track without tags. */ + send_flush_events (element); + send_segment_event (element); + for (i = 20; i--;) + push_buffer (test_buffer_square_float_stereo (&accumulator, 44100, 512, + 0.25, 0.25)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.25); + fail_unless_track_gain (tag_list, get_expected_gain (44100)); + gst_tag_list_unref (tag_list); + + cleanup_rganalysis (element); +} + +GST_END_TEST; + +/* Sending track gain and peak in separate tag lists. */ + +GST_START_TEST (test_forced_separate) +{ + GstElement *element = setup_rganalysis (); + GstTagList *tag_list; + gint accumulator = 0; + gint i; + + g_object_set (element, "forced", FALSE, NULL); + set_playing_state (element); + + send_stream_start_event (element); + send_caps_event (GST_AUDIO_NE (F32), 44100, 2); + send_segment_event (element); + tag_list = gst_tag_list_new_empty (); + gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, GST_TAG_TRACK_GAIN, 2.21, + NULL); + send_tag_event (element, tag_list); + + tag_list = gst_tag_list_new_empty (); + gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, GST_TAG_TRACK_PEAK, 1.0, + NULL); + send_tag_event (element, tag_list); + + for (i = 20; i--;) + push_buffer (test_buffer_square_float_stereo (&accumulator, 44100, 512, + 0.5, 0.5)); + send_eos_event (element); + + /* Same values as above */ + tag_list = poll_tags_only (element); + fail_unless_track_gain (tag_list, 2.21); + gst_tag_list_unref (tag_list); + + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 1.0); + gst_tag_list_unref (tag_list); + + /* Now a track without tags. */ + send_flush_events (element); + send_segment_event (element); + accumulator = 0; + for (i = 20; i--;) + push_buffer (test_buffer_square_float_stereo (&accumulator, 44100, 512, + 0.25, 0.25)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.25); + fail_unless_track_gain (tag_list, get_expected_gain (44100)); + fail_if_album_tags (tag_list); + gst_tag_list_unref (tag_list); + + cleanup_rganalysis (element); +} + +GST_END_TEST; + +/* A TAG event is sent _after_ data has already been processed. In real + * pipelines, this could happen if there is more than one rganalysis element (by + * accident). While it would have analyzed all the data prior to receiving the + * event, I expect it to not post its results if not forced. This test is + * almost equivalent to test_forced. */ + +GST_START_TEST (test_forced_after_data) +{ + GstElement *element = setup_rganalysis (); + GstTagList *tag_list; + gint accumulator = 0; + gint i; + + g_object_set (element, "forced", FALSE, NULL); + set_playing_state (element); + + send_stream_start_event (element); + send_caps_event (GST_AUDIO_NE (F32), 8000, 2); + send_segment_event (element); + for (i = 20; i--;) + push_buffer (test_buffer_const_float_stereo (8000, 512, 0.5, 0.5)); + + tag_list = gst_tag_list_new_empty (); + gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, + GST_TAG_TRACK_PEAK, 1.0, GST_TAG_TRACK_GAIN, 2.21, NULL); + send_tag_event (element, tag_list); + + send_eos_event (element); + + /* Same values as above */ + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_gain (tag_list, 2.21); + fail_unless_track_peak (tag_list, 1.0); + gst_tag_list_unref (tag_list); + + send_flush_events (element); + send_segment_event (element); + /* Now back to a normal track, this one has no tags: */ + for (i = 20; i--;) + push_buffer (test_buffer_square_float_stereo (&accumulator, 8000, 512, 0.25, + 0.25)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.25); + fail_unless_track_gain (tag_list, get_expected_gain (8000)); + gst_tag_list_unref (tag_list); + + cleanup_rganalysis (element); +} + +GST_END_TEST; + +/* Like test_forced, but *analyze* an album afterwards. The two tests following + * this one check the *skipping* of albums. */ + +GST_START_TEST (test_forced_album) +{ + GstElement *element = setup_rganalysis (); + GstTagList *tag_list; + gint accumulator; + gint i; + + g_object_set (element, "forced", FALSE, NULL); + set_playing_state (element); + + send_stream_start_event (element); + send_caps_event (GST_AUDIO_NE (F32), 44100, 2); + send_segment_event (element); + tag_list = gst_tag_list_new_empty (); + /* Provided values are totally arbitrary. */ + gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, + GST_TAG_TRACK_PEAK, 1.0, GST_TAG_TRACK_GAIN, 2.21, NULL); + send_tag_event (element, tag_list); + + accumulator = 0; + for (i = 20; i--;) + push_buffer (test_buffer_square_float_stereo (&accumulator, 44100, 512, + 0.5, 0.5)); + send_eos_event (element); + + /* Same values as above */ + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_gain (tag_list, 2.21); + fail_unless_track_peak (tag_list, 1.0); + gst_tag_list_unref (tag_list); + + /* Now an album without tags. */ + g_object_set (element, "num-tracks", 2, NULL); + + send_flush_events (element); + send_segment_event (element); + accumulator = 0; + for (i = 20; i--;) + push_buffer (test_buffer_square_float_stereo (&accumulator, 44100, 512, + 0.25, 0.25)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.25); + fail_unless_track_gain (tag_list, get_expected_gain (44100)); + fail_if_album_tags (tag_list); + gst_tag_list_unref (tag_list); + fail_unless_num_tracks (element, 1); + + send_flush_events (element); + send_segment_event (element); + accumulator = 0; + for (i = 20; i--;) + push_buffer (test_buffer_square_float_stereo (&accumulator, 44100, 512, + 0.25, 0.25)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.25); + fail_unless_track_gain (tag_list, get_expected_gain (44100)); + fail_unless_album_peak (tag_list, 0.25); + fail_unless_album_gain (tag_list, get_expected_gain (44100)); + gst_tag_list_unref (tag_list); + fail_unless_num_tracks (element, 0); + + cleanup_rganalysis (element); +} + +GST_END_TEST; + +GST_START_TEST (test_forced_album_skip) +{ + GstElement *element = setup_rganalysis (); + GstTagList *tag_list; + gint accumulator = 0; + gint i; + + g_object_set (element, "forced", FALSE, "num-tracks", 2, NULL); + set_playing_state (element); + + send_stream_start_event (element); + send_caps_event (GST_AUDIO_NE (F32), 8000, 2); + send_segment_event (element); + tag_list = gst_tag_list_new_empty (); + /* Provided values are totally arbitrary. */ + gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, + GST_TAG_TRACK_PEAK, 0.75, GST_TAG_TRACK_GAIN, 2.21, + GST_TAG_ALBUM_PEAK, 0.80, GST_TAG_ALBUM_GAIN, -0.11, NULL); + send_tag_event (element, tag_list); + + for (i = 20; i--;) + push_buffer (test_buffer_square_float_stereo (&accumulator, 8000, 512, 0.25, + 0.25)); + send_eos_event (element); + + /* Same values as above */ + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_gain (tag_list, 2.21); + fail_unless_track_peak (tag_list, 0.75); + gst_tag_list_unref (tag_list); + + fail_unless_num_tracks (element, 1); + + /* This track has no tags, but needs to be skipped anyways since we + * are in album processing mode. */ + send_flush_events (element); + send_segment_event (element); + for (i = 20; i--;) + push_buffer (test_buffer_const_float_stereo (8000, 512, 0.0, 0.0)); + send_eos_event (element); + poll_eos (element); + fail_unless_num_tracks (element, 0); + + /* Normal track after the album. Of course not to be skipped. */ + send_flush_events (element); + send_segment_event (element); + accumulator = 0; + for (i = 20; i--;) + push_buffer (test_buffer_square_float_stereo (&accumulator, 8000, 512, 0.25, + 0.25)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.25); + fail_unless_track_gain (tag_list, get_expected_gain (8000)); + fail_if_album_tags (tag_list); + gst_tag_list_unref (tag_list); + + cleanup_rganalysis (element); +} + +GST_END_TEST; + +GST_START_TEST (test_forced_album_no_skip) +{ + GstElement *element = setup_rganalysis (); + GstTagList *tag_list; + gint accumulator = 0; + gint i; + + g_object_set (element, "forced", FALSE, "num-tracks", 2, NULL); + set_playing_state (element); + + send_stream_start_event (element); + send_caps_event (GST_AUDIO_NE (F32), 8000, 2); + send_segment_event (element); + for (i = 20; i--;) + push_buffer (test_buffer_square_float_stereo (&accumulator, 8000, 512, 0.25, + 0.25)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.25); + fail_unless_track_gain (tag_list, get_expected_gain (8000)); + fail_if_album_tags (tag_list); + gst_tag_list_unref (tag_list); + fail_unless_num_tracks (element, 1); + + /* The second track has indeed full tags, but although being not forced, this + * one has to be processed because album processing is on. */ + send_flush_events (element); + send_segment_event (element); + tag_list = gst_tag_list_new_empty (); + /* Provided values are totally arbitrary. */ + gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, + GST_TAG_TRACK_PEAK, 0.75, GST_TAG_TRACK_GAIN, 2.21, + GST_TAG_ALBUM_PEAK, 0.80, GST_TAG_ALBUM_GAIN, -0.11, NULL); + send_tag_event (element, tag_list); + for (i = 20; i--;) + push_buffer (test_buffer_const_float_stereo (8000, 512, 0.0, 0.0)); + send_eos_event (element); + + /* the first batch from the tags */ + tag_list = poll_tags_only (element); + fail_unless_track_peak (tag_list, 0.75); + fail_unless_track_gain (tag_list, 2.21); + gst_tag_list_unref (tag_list); + + /* the second from the processing */ + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.0); + fail_unless_track_gain (tag_list, SILENCE_GAIN); + /* Second track was just silence so the album peak equals the first + * track's peak. */ + fail_unless_album_peak (tag_list, 0.25); + /* Statistical processing leads to the second track being + * ignored for the gain (because it is so short): */ + fail_unless_album_gain (tag_list, get_expected_gain (8000)); + gst_tag_list_unref (tag_list); + fail_unless_num_tracks (element, 0); + + cleanup_rganalysis (element); +} + +GST_END_TEST; + +GST_START_TEST (test_forced_abort_album_no_skip) +{ + GstElement *element = setup_rganalysis (); + GstTagList *tag_list; + gint accumulator = 0; + gint i; + + g_object_set (element, "forced", FALSE, "num-tracks", 2, NULL); + set_playing_state (element); + + send_stream_start_event (element); + send_caps_event (GST_AUDIO_NE (F32), 8000, 2); + send_segment_event (element); + for (i = 20; i--;) + push_buffer (test_buffer_square_float_stereo (&accumulator, 8000, 512, 0.25, + 0.25)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.25); + fail_unless_track_gain (tag_list, get_expected_gain (8000)); + fail_if_album_tags (tag_list); + gst_tag_list_unref (tag_list); + fail_unless_num_tracks (element, 1); + + /* Disabling album processing before end of album: */ + g_object_set (element, "num-tracks", 0, NULL); + + send_flush_events (element); + send_segment_event (element); + + /* Processing a track that has to be skipped. */ + tag_list = gst_tag_list_new_empty (); + /* Provided values are totally arbitrary. */ + gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, + GST_TAG_TRACK_PEAK, 0.75, GST_TAG_TRACK_GAIN, 2.21, + GST_TAG_ALBUM_PEAK, 0.80, GST_TAG_ALBUM_GAIN, -0.11, NULL); + send_tag_event (element, tag_list); + for (i = 20; i--;) + push_buffer (test_buffer_const_float_stereo (8000, 512, 0.0, 0.0)); + send_eos_event (element); + + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.75); + fail_unless_track_gain (tag_list, 2.21); + gst_tag_list_unref (tag_list); + + cleanup_rganalysis (element); +} + +GST_END_TEST; + +GST_START_TEST (test_reference_level) +{ + GstElement *element = setup_rganalysis (); + GstTagList *tag_list; + gdouble ref_level; + gint accumulator = 0; + gint i; + + set_playing_state (element); + + send_stream_start_event (element); + send_caps_event (GST_AUDIO_NE (F32), 44100, 2); + send_segment_event (element); + for (i = 20; i--;) + push_buffer (test_buffer_square_float_stereo (&accumulator, 44100, 512, + 0.25, 0.25)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.25); + fail_unless_track_gain (tag_list, get_expected_gain (44100)); + fail_if_album_tags (tag_list); + fail_unless (gst_tag_list_get_double (tag_list, GST_TAG_REFERENCE_LEVEL, + &ref_level) && MATCH_GAIN (ref_level, 89.), + "Incorrect reference level tag"); + gst_tag_list_unref (tag_list); + + g_object_set (element, "reference-level", 83., "num-tracks", 2, NULL); + + send_flush_events (element); + send_segment_event (element); + for (i = 20; i--;) + push_buffer (test_buffer_square_float_stereo (&accumulator, 44100, 512, + 0.25, 0.25)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.25); + fail_unless_track_gain (tag_list, get_expected_gain (44100) - 6.); + fail_if_album_tags (tag_list); + fail_unless (gst_tag_list_get_double (tag_list, GST_TAG_REFERENCE_LEVEL, + &ref_level) && MATCH_GAIN (ref_level, 83.), + "Incorrect reference level tag"); + gst_tag_list_unref (tag_list); + + send_flush_events (element); + send_segment_event (element); + accumulator = 0; + for (i = 20; i--;) + push_buffer (test_buffer_square_float_stereo (&accumulator, 44100, 512, + 0.25, 0.25)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.25); + fail_unless_track_gain (tag_list, get_expected_gain (44100) - 6.); + fail_unless_album_peak (tag_list, 0.25); + /* We provided the same waveform twice, with a reset separating + * them. Therefore, the album gain matches the track gain. */ + fail_unless_album_gain (tag_list, get_expected_gain (44100) - 6.); + fail_unless (gst_tag_list_get_double (tag_list, GST_TAG_REFERENCE_LEVEL, + &ref_level) && MATCH_GAIN (ref_level, 83.), + "Incorrect reference level tag"); + gst_tag_list_unref (tag_list); + + cleanup_rganalysis (element); +} + +GST_END_TEST; + +GST_START_TEST (test_all_formats) +{ + GstElement *element = setup_rganalysis (); + GstTagList *tag_list; + gint accumulator = 0; + gint i, j; + + set_playing_state (element); + send_stream_start_event (element); + for (i = G_N_ELEMENTS (supported_rates); i--;) { + send_flush_events (element); + send_caps_event (GST_AUDIO_NE (F32), supported_rates[i].sample_rate, 2); + send_segment_event (element); + accumulator = 0; + for (j = 0; j < 4; j++) + push_buffer (test_buffer_square_float_stereo (&accumulator, + supported_rates[i].sample_rate, 512, 0.25, 0.25)); + send_caps_event (GST_AUDIO_NE (F32), supported_rates[i].sample_rate, 1); + for (j = 0; j < 3; j++) + push_buffer (test_buffer_square_float_mono (&accumulator, + supported_rates[i].sample_rate, 512, 0.25)); + send_caps_event (GST_AUDIO_NE (S16), supported_rates[i].sample_rate, 2); + for (j = 0; j < 4; j++) + push_buffer (test_buffer_square_int16_stereo (&accumulator, + supported_rates[i].sample_rate, 16, 512, 1 << 13, 1 << 13)); + send_caps_event (GST_AUDIO_NE (S16), supported_rates[i].sample_rate, 1); + for (j = 0; j < 3; j++) + push_buffer (test_buffer_square_int16_mono (&accumulator, + supported_rates[i].sample_rate, 16, 512, 1 << 13)); + send_eos_event (element); + tag_list = poll_tags_followed_by_eos (element); + fail_unless_track_peak (tag_list, 0.25); + fail_unless_track_gain (tag_list, supported_rates[i].gain); + gst_tag_list_unref (tag_list); + } + + cleanup_rganalysis (element); +} + +GST_END_TEST; + +/* Checks ensuring all advertised supported sample rates are really + * accepted, for integer and float, mono and stereo. This also + * verifies that the correct gain is computed for all formats (except + * odd bit depths). */ + +#define MAKE_GAIN_TEST_FLOAT_MONO(sample_rate) \ + GST_START_TEST (test_gain_float_mono_##sample_rate) \ +{ \ + GstElement *element = setup_rganalysis (); \ + GstTagList *tag_list; \ + gint accumulator = 0; \ + gint i; \ + \ + set_playing_state (element); \ + send_stream_start_event (element); \ + send_caps_event (GST_AUDIO_NE (F32), sample_rate, 1); \ + send_segment_event (element); \ + \ + for (i = 0; i < 20; i++) \ + push_buffer (test_buffer_square_float_mono (&accumulator, \ + sample_rate, 512, 0.25)); \ + send_eos_event (element); \ + tag_list = poll_tags_followed_by_eos (element); \ + fail_unless_track_peak (tag_list, 0.25); \ + fail_unless_track_gain (tag_list, \ + get_expected_gain (sample_rate)); \ + gst_tag_list_unref (tag_list); \ + \ + cleanup_rganalysis (element); \ +} \ + \ +GST_END_TEST; + +#define MAKE_GAIN_TEST_FLOAT_STEREO(sample_rate) \ + GST_START_TEST (test_gain_float_stereo_##sample_rate) \ +{ \ + GstElement *element = setup_rganalysis (); \ + GstTagList *tag_list; \ + gint accumulator = 0; \ + gint i; \ + \ + set_playing_state (element); \ + send_stream_start_event (element); \ + send_caps_event (GST_AUDIO_NE (F32), sample_rate, 2); \ + send_segment_event (element); \ + \ + for (i = 0; i < 20; i++) \ + push_buffer (test_buffer_square_float_stereo (&accumulator, \ + sample_rate, 512, 0.25, 0.25)); \ + send_eos_event (element); \ + tag_list = poll_tags_followed_by_eos (element); \ + fail_unless_track_peak (tag_list, 0.25); \ + fail_unless_track_gain (tag_list, \ + get_expected_gain (sample_rate)); \ + gst_tag_list_unref (tag_list); \ + \ + cleanup_rganalysis (element); \ +} \ + \ +GST_END_TEST; + +#define MAKE_GAIN_TEST_INT16_MONO(sample_rate, depth) \ + GST_START_TEST (test_gain_int16_##depth##_mono_##sample_rate) \ +{ \ + GstElement *element = setup_rganalysis (); \ + GstTagList *tag_list; \ + gint accumulator = 0; \ + gint i; \ + \ + set_playing_state (element); \ + send_stream_start_event (element); \ + send_caps_event (GST_AUDIO_NE (S16), sample_rate, 1); \ + send_segment_event (element); \ + \ + for (i = 0; i < 20; i++) \ + push_buffer (test_buffer_square_int16_mono (&accumulator, \ + sample_rate, depth, 512, 1 << (13 + depth - 16))); \ + \ + send_eos_event (element); \ + tag_list = poll_tags_followed_by_eos (element); \ + fail_unless_track_peak (tag_list, 0.25); \ + fail_unless_track_gain (tag_list, \ + get_expected_gain (sample_rate)); \ + gst_tag_list_unref (tag_list); \ + \ + cleanup_rganalysis (element); \ +} \ + \ +GST_END_TEST; + +#define MAKE_GAIN_TEST_INT16_STEREO(sample_rate, depth) \ + GST_START_TEST (test_gain_int16_##depth##_stereo_##sample_rate) \ +{ \ + GstElement *element = setup_rganalysis (); \ + GstTagList *tag_list; \ + gint accumulator = 0; \ + gint i; \ + \ + set_playing_state (element); \ + send_stream_start_event (element); \ + send_caps_event (GST_AUDIO_NE (S16), sample_rate, 2); \ + send_segment_event (element); \ + \ + for (i = 0; i < 20; i++) \ + push_buffer (test_buffer_square_int16_stereo (&accumulator, \ + sample_rate, depth, 512, 1 << (13 + depth - 16), \ + 1 << (13 + depth - 16))); \ + send_eos_event (element); \ + tag_list = poll_tags_followed_by_eos (element); \ + fail_unless_track_peak (tag_list, 0.25); \ + fail_unless_track_gain (tag_list, \ + get_expected_gain (sample_rate)); \ + gst_tag_list_unref (tag_list); \ + \ + cleanup_rganalysis (element); \ +} \ + \ +GST_END_TEST; + +MAKE_GAIN_TEST_FLOAT_MONO (8000); +MAKE_GAIN_TEST_FLOAT_MONO (11025); +MAKE_GAIN_TEST_FLOAT_MONO (12000); +MAKE_GAIN_TEST_FLOAT_MONO (16000); +MAKE_GAIN_TEST_FLOAT_MONO (22050); +MAKE_GAIN_TEST_FLOAT_MONO (24000); +MAKE_GAIN_TEST_FLOAT_MONO (32000); +MAKE_GAIN_TEST_FLOAT_MONO (44100); +MAKE_GAIN_TEST_FLOAT_MONO (48000); + +MAKE_GAIN_TEST_FLOAT_STEREO (8000); +MAKE_GAIN_TEST_FLOAT_STEREO (11025); +MAKE_GAIN_TEST_FLOAT_STEREO (12000); +MAKE_GAIN_TEST_FLOAT_STEREO (16000); +MAKE_GAIN_TEST_FLOAT_STEREO (22050); +MAKE_GAIN_TEST_FLOAT_STEREO (24000); +MAKE_GAIN_TEST_FLOAT_STEREO (32000); +MAKE_GAIN_TEST_FLOAT_STEREO (44100); +MAKE_GAIN_TEST_FLOAT_STEREO (48000); + +MAKE_GAIN_TEST_INT16_MONO (8000, 16); +MAKE_GAIN_TEST_INT16_MONO (11025, 16); +MAKE_GAIN_TEST_INT16_MONO (12000, 16); +MAKE_GAIN_TEST_INT16_MONO (16000, 16); +MAKE_GAIN_TEST_INT16_MONO (22050, 16); +MAKE_GAIN_TEST_INT16_MONO (24000, 16); +MAKE_GAIN_TEST_INT16_MONO (32000, 16); +MAKE_GAIN_TEST_INT16_MONO (44100, 16); +MAKE_GAIN_TEST_INT16_MONO (48000, 16); + +MAKE_GAIN_TEST_INT16_STEREO (8000, 16); +MAKE_GAIN_TEST_INT16_STEREO (11025, 16); +MAKE_GAIN_TEST_INT16_STEREO (12000, 16); +MAKE_GAIN_TEST_INT16_STEREO (16000, 16); +MAKE_GAIN_TEST_INT16_STEREO (22050, 16); +MAKE_GAIN_TEST_INT16_STEREO (24000, 16); +MAKE_GAIN_TEST_INT16_STEREO (32000, 16); +MAKE_GAIN_TEST_INT16_STEREO (44100, 16); +MAKE_GAIN_TEST_INT16_STEREO (48000, 16); + +static Suite * +rganalysis_suite (void) +{ + Suite *s = suite_create ("rganalysis"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_no_buffer); + tcase_add_test (tc_chain, test_no_buffer_album_1); + tcase_add_test (tc_chain, test_no_buffer_album_2); + tcase_add_test (tc_chain, test_empty_buffers); + + tcase_add_test (tc_chain, test_peak_float); + tcase_add_test (tc_chain, test_peak_int16_16); + + tcase_add_test (tc_chain, test_peak_album); + tcase_add_test (tc_chain, test_peak_track_album); + tcase_add_test (tc_chain, test_peak_album_abort_to_track); + + tcase_add_test (tc_chain, test_gain_album); + + tcase_add_test (tc_chain, test_forced); + tcase_add_test (tc_chain, test_forced_separate); + tcase_add_test (tc_chain, test_forced_after_data); + tcase_add_test (tc_chain, test_forced_album); + tcase_add_test (tc_chain, test_forced_album_skip); + tcase_add_test (tc_chain, test_forced_album_no_skip); + tcase_add_test (tc_chain, test_forced_abort_album_no_skip); + + tcase_add_test (tc_chain, test_reference_level); + + tcase_add_test (tc_chain, test_all_formats); + + tcase_add_test (tc_chain, test_gain_float_mono_8000); + tcase_add_test (tc_chain, test_gain_float_mono_11025); + tcase_add_test (tc_chain, test_gain_float_mono_12000); + tcase_add_test (tc_chain, test_gain_float_mono_16000); + tcase_add_test (tc_chain, test_gain_float_mono_22050); + tcase_add_test (tc_chain, test_gain_float_mono_24000); + tcase_add_test (tc_chain, test_gain_float_mono_32000); + tcase_add_test (tc_chain, test_gain_float_mono_44100); + tcase_add_test (tc_chain, test_gain_float_mono_48000); + + tcase_add_test (tc_chain, test_gain_float_stereo_8000); + tcase_add_test (tc_chain, test_gain_float_stereo_11025); + tcase_add_test (tc_chain, test_gain_float_stereo_12000); + tcase_add_test (tc_chain, test_gain_float_stereo_16000); + tcase_add_test (tc_chain, test_gain_float_stereo_22050); + tcase_add_test (tc_chain, test_gain_float_stereo_24000); + tcase_add_test (tc_chain, test_gain_float_stereo_32000); + tcase_add_test (tc_chain, test_gain_float_stereo_44100); + tcase_add_test (tc_chain, test_gain_float_stereo_48000); + + tcase_add_test (tc_chain, test_gain_int16_16_mono_8000); + tcase_add_test (tc_chain, test_gain_int16_16_mono_11025); + tcase_add_test (tc_chain, test_gain_int16_16_mono_12000); + tcase_add_test (tc_chain, test_gain_int16_16_mono_16000); + tcase_add_test (tc_chain, test_gain_int16_16_mono_22050); + tcase_add_test (tc_chain, test_gain_int16_16_mono_24000); + tcase_add_test (tc_chain, test_gain_int16_16_mono_32000); + tcase_add_test (tc_chain, test_gain_int16_16_mono_44100); + tcase_add_test (tc_chain, test_gain_int16_16_mono_48000); + + tcase_add_test (tc_chain, test_gain_int16_16_stereo_8000); + tcase_add_test (tc_chain, test_gain_int16_16_stereo_11025); + tcase_add_test (tc_chain, test_gain_int16_16_stereo_12000); + tcase_add_test (tc_chain, test_gain_int16_16_stereo_16000); + tcase_add_test (tc_chain, test_gain_int16_16_stereo_22050); + tcase_add_test (tc_chain, test_gain_int16_16_stereo_24000); + tcase_add_test (tc_chain, test_gain_int16_16_stereo_32000); + tcase_add_test (tc_chain, test_gain_int16_16_stereo_44100); + tcase_add_test (tc_chain, test_gain_int16_16_stereo_48000); + + return s; +} + +int +main (int argc, char **argv) +{ + gint nf; + + Suite *s = rganalysis_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_ENV); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/rglimiter.c b/tests/check/elements/rglimiter.c new file mode 100644 index 0000000..926ca33 --- /dev/null +++ b/tests/check/elements/rglimiter.c @@ -0,0 +1,292 @@ +/* GStreamer ReplayGain limiter + * + * Copyright (C) 2007 Rene Stadler <mail@renestadler.de> + * + * rglimiter.c: Unit test for the rglimiter element + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <gst/check/gstcheck.h> +#include <gst/audio/audio.h> + +#include <math.h> + +/* 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; + +#define RG_LIMITER_CAPS_TEMPLATE_STRING \ + "audio/x-raw, " \ + "format = (string) "GST_AUDIO_NE (F32) ", " \ + "layout = (string) interleaved, " \ + "channels = (int) [ 1, MAX ], " \ + "rate = (int) [ 1, MAX ]" + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (RG_LIMITER_CAPS_TEMPLATE_STRING) + ); +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (RG_LIMITER_CAPS_TEMPLATE_STRING) + ); + +static GstElement * +setup_rglimiter (void) +{ + GstElement *element; + + GST_DEBUG ("setup_rglimiter"); + element = gst_check_setup_element ("rglimiter"); + mysrcpad = gst_check_setup_src_pad (element, &srctemplate); + mysinkpad = gst_check_setup_sink_pad (element, &sinktemplate); + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + + return element; +} + +static void +cleanup_rglimiter (GstElement * element) +{ + GST_DEBUG ("cleanup_rglimiter"); + + g_list_foreach (buffers, (GFunc) gst_mini_object_unref, NULL); + g_list_free (buffers); + buffers = NULL; + + gst_check_teardown_src_pad (element); + gst_check_teardown_sink_pad (element); + gst_check_teardown_element (element); +} + +static void +set_playing_state (GstElement * element) +{ + fail_unless (gst_element_set_state (element, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "Could not set state to PLAYING"); +} + +static const gfloat test_input[] = { + -2.0, -1.0, -0.75, -0.5, -0.25, 0.0, 0.25, 0.5, 0.75, 1.0, 2.0 +}; + +static const gfloat test_output[] = { + -0.99752737684336523, /* -2.0 */ + -0.88079707797788243, /* -1.0 */ + -0.7310585786300049, /* -0.75 */ + -0.5, -0.25, 0.0, 0.25, 0.5, + 0.7310585786300049, /* 0.75 */ + 0.88079707797788243, /* 1.0 */ + 0.99752737684336523, /* 2.0 */ +}; + +static void +setup_events (GstElement * element) +{ + GstCaps *caps; + + caps = gst_caps_new_simple ("audio/x-raw", + "rate", G_TYPE_INT, 44100, "channels", G_TYPE_INT, 1, + "format", G_TYPE_STRING, GST_AUDIO_NE (F32), + "layout", G_TYPE_STRING, "interleaved", NULL); + + gst_check_setup_events (mysrcpad, element, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); +} + +static GstBuffer * +create_test_buffer (void) +{ + GstBuffer *buf = gst_buffer_new_and_alloc (sizeof (test_input)); + + gst_buffer_fill (buf, 0, test_input, sizeof (test_input)); + + ASSERT_BUFFER_REFCOUNT (buf, "buf", 1); + + return buf; +} + +static void +verify_test_buffer (GstBuffer * buf) +{ + GstMapInfo map; + gfloat *output; + gint i; + + gst_buffer_map (buf, &map, GST_MAP_READ); + output = (gfloat *) map.data; + fail_unless (map.size == sizeof (test_output)); + + for (i = 0; i < G_N_ELEMENTS (test_input); i++) + fail_unless (ABS (output[i] - test_output[i]) < 1.e-6, + "Incorrect output value %.6f for input %.2f, expected %.6f", + output[i], test_input[i], test_output[i]); + + gst_buffer_unmap (buf, &map); +} + +/* Start of tests. */ + +GST_START_TEST (test_no_buffer) +{ + GstElement *element = setup_rglimiter (); + + set_playing_state (element); + + cleanup_rglimiter (element); +} + +GST_END_TEST; + +GST_START_TEST (test_disabled) +{ + GstElement *element = setup_rglimiter (); + GstBuffer *buf, *out_buf; + + g_object_set (element, "enabled", FALSE, NULL); + set_playing_state (element); + setup_events (element); + + buf = create_test_buffer (); + fail_unless (gst_pad_push (mysrcpad, buf) == GST_FLOW_OK); + fail_unless (g_list_length (buffers) == 1); + out_buf = buffers->data; + fail_if (out_buf == NULL); + buffers = g_list_remove (buffers, out_buf); + ASSERT_BUFFER_REFCOUNT (out_buf, "out_buf", 1); + fail_unless (buf == out_buf); + gst_buffer_unref (out_buf); + + cleanup_rglimiter (element); +} + +GST_END_TEST; + +GST_START_TEST (test_limiting) +{ + GstElement *element = setup_rglimiter (); + GstBuffer *buf, *out_buf; + + set_playing_state (element); + setup_events (element); + + /* Mutable variant. */ + buf = create_test_buffer (); + GST_DEBUG ("push mutable buffer"); + fail_unless (gst_pad_push (mysrcpad, buf) == GST_FLOW_OK); + fail_unless (g_list_length (buffers) == 1); + out_buf = buffers->data; + fail_if (out_buf == NULL); + ASSERT_BUFFER_REFCOUNT (out_buf, "out_buf", 1); + verify_test_buffer (out_buf); + + /* Immutable variant. */ + buf = create_test_buffer (); + /* Extra ref: */ + gst_buffer_ref (buf); + ASSERT_BUFFER_REFCOUNT (buf, "buf", 2); + GST_DEBUG ("push immutable buffer"); + fail_unless (gst_pad_push (mysrcpad, buf) == GST_FLOW_OK); + ASSERT_BUFFER_REFCOUNT (buf, "buf", 1); + fail_unless (g_list_length (buffers) == 2); + out_buf = g_list_last (buffers)->data; + fail_if (out_buf == NULL); + ASSERT_BUFFER_REFCOUNT (out_buf, "out_buf", 1); + fail_unless (buf != out_buf); + /* Drop our extra ref: */ + gst_buffer_unref (buf); + verify_test_buffer (out_buf); + + cleanup_rglimiter (element); +} + +GST_END_TEST; + +GST_START_TEST (test_gap) +{ + GstElement *element = setup_rglimiter (); + GstBuffer *buf, *out_buf; + GstMapInfo m1, m2; + + set_playing_state (element); + setup_events (element); + + buf = create_test_buffer (); + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_GAP); + fail_unless (gst_pad_push (mysrcpad, buf) == GST_FLOW_OK); + fail_unless (g_list_length (buffers) == 1); + out_buf = buffers->data; + fail_if (out_buf == NULL); + ASSERT_BUFFER_REFCOUNT (out_buf, "out_buf", 1); + + /* Verify that the baseclass does not lift the GAP flag: */ + fail_unless (GST_BUFFER_FLAG_IS_SET (out_buf, GST_BUFFER_FLAG_GAP)); + + gst_buffer_map (out_buf, &m1, GST_MAP_READ); + gst_buffer_map (buf, &m2, GST_MAP_READ); + + g_assert (m1.size == m2.size); + /* We cheated by passing an input buffer with non-silence that has the GAP + * flag set. The element cannot know that however and must have skipped + * adjusting the buffer because of the flag, which we can easily verify: */ + fail_if (memcmp (m1.data, m2.data, m1.size) != 0); + + gst_buffer_unmap (out_buf, &m1); + gst_buffer_unmap (buf, &m2); + + cleanup_rglimiter (element); +} + +GST_END_TEST; + +static Suite * +rglimiter_suite (void) +{ + Suite *s = suite_create ("rglimiter"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_no_buffer); + tcase_add_test (tc_chain, test_disabled); + tcase_add_test (tc_chain, test_limiting); + tcase_add_test (tc_chain, test_gap); + + return s; +} + +int +main (int argc, char **argv) +{ + gint nf; + + Suite *s = rglimiter_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_ENV); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/rgvolume.c b/tests/check/elements/rgvolume.c new file mode 100644 index 0000000..47ed3b3 --- /dev/null +++ b/tests/check/elements/rgvolume.c @@ -0,0 +1,687 @@ +/* GStreamer ReplayGain volume adjustment + * + * Copyright (C) 2007 Rene Stadler <mail@renestadler.de> + * + * rgvolume.c: Unit test for the rgvolume element + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <gst/check/gstcheck.h> +#include <gst/audio/audio.h> + +#include <math.h> + +static GList *events = NULL; + +/* 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; + +#define RG_VOLUME_CAPS_TEMPLATE_STRING \ + "audio/x-raw, " \ + "format = (string) "GST_AUDIO_NE (F32) ", " \ + "layout = (string) interleaved, " \ + "channels = (int) [ 1, MAX ], " \ + "rate = (int) [ 1, MAX ]" + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (RG_VOLUME_CAPS_TEMPLATE_STRING) + ); +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (RG_VOLUME_CAPS_TEMPLATE_STRING) + ); + +static GstBuffer *test_buffer_new (gfloat value); + +/* gstcheck sets up a chain function that appends buffers to a global list. + * This is our equivalent of that for event handling. */ +static gboolean +event_func (GstPad * pad, GstObject * parent, GstEvent * event) +{ + GST_DEBUG ("received event %p (%s)", event, GST_EVENT_TYPE_NAME (event)); + events = g_list_append (events, event); + + return TRUE; +} + +static GstElement * +setup_rgvolume (void) +{ + GstElement *element; + + GST_DEBUG ("setup_rgvolume"); + element = gst_check_setup_element ("rgvolume"); + mysrcpad = gst_check_setup_src_pad (element, &srctemplate); + mysinkpad = gst_check_setup_sink_pad (element, &sinktemplate); + + /* Capture events, to test tag filtering behavior: */ + gst_pad_set_event_function (mysinkpad, event_func); + + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + + return element; +} + +static void +send_empty_buffer (void) +{ + GstBuffer *buf; + + buf = test_buffer_new (0.0); + gst_buffer_resize (buf, 0, 0); + GST_BUFFER_DURATION (buf) = 0; + GST_BUFFER_OFFSET_END (buf) = GST_BUFFER_OFFSET (buf); + fail_unless (gst_pad_push (mysrcpad, buf) == GST_FLOW_OK); + + fail_unless (g_list_length (buffers) == 1); + fail_unless (buffers->data == buf); + gst_mini_object_unref ((GstMiniObject *) buffers->data); + buffers = g_list_remove (buffers, buf); +} + +static void +cleanup_rgvolume (GstElement * element) +{ + GST_DEBUG ("cleanup_rgvolume"); + + g_list_foreach (buffers, (GFunc) gst_mini_object_unref, NULL); + g_list_free (buffers); + buffers = NULL; + + g_list_foreach (events, (GFunc) gst_mini_object_unref, NULL); + g_list_free (events); + events = NULL; + + gst_pad_set_active (mysrcpad, FALSE); + gst_pad_set_active (mysinkpad, FALSE); + gst_check_teardown_src_pad (element); + gst_check_teardown_sink_pad (element); + gst_check_teardown_element (element); +} + +static void +set_playing_state (GstElement * element) +{ + fail_unless (gst_element_set_state (element, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "Could not set state to PLAYING"); +} + +static void +set_null_state (GstElement * element) +{ + fail_unless (gst_element_set_state (element, + GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS, + "Could not set state to NULL"); +} + +static void +send_flush_events (GstElement * element) +{ + gboolean res; + + res = gst_pad_push_event (mysrcpad, gst_event_new_flush_start ()); + fail_unless (res, "flush-start even not handled"); + + res = gst_pad_push_event (mysrcpad, gst_event_new_flush_stop (TRUE)); + fail_unless (res, "flush-stop event not handled"); +} + +static void +send_stream_start_event (GstElement * element) +{ + gboolean res; + + res = gst_pad_push_event (mysrcpad, gst_event_new_stream_start ("test")); + fail_unless (res, "STREAM_START event not handled"); +} + +static void +send_caps_event (GstElement * element) +{ + GstCaps *caps; + gboolean res; + + caps = gst_caps_from_string ("audio/x-raw, format = F32LE, " + "layout = interleaved, rate = 8000, channels = 1"); + res = gst_pad_push_event (mysrcpad, gst_event_new_caps (caps)); + fail_unless (res, "CAPS event not handled"); + gst_caps_unref (caps); +} + +static void +send_segment_event (GstElement * element) +{ + GstSegment segment; + gboolean res; + + gst_segment_init (&segment, GST_FORMAT_TIME); + res = gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment)); + fail_unless (res, "SEGMENT event not handled"); +} + +static void +send_eos_event (GstElement * element) +{ + GstEvent *event = gst_event_new_eos (); + + fail_unless (gst_pad_push_event (mysrcpad, event), + "Pushing EOS event failed"); +} + +static GstEvent * +send_tag_event (GstElement * element, GstEvent * event) +{ + GList *l; + GstTagList *tag_list; + gdouble dummy; + + g_return_val_if_fail (event->type == GST_EVENT_TAG, NULL); + + fail_unless (gst_pad_push_event (mysrcpad, event), + "Pushing tag event failed"); + + event = NULL; + + for (l = g_list_last (events); l; l = l->prev) { + if (GST_EVENT_TYPE (l->data) == GST_EVENT_TAG) { + event = l->data; + events = g_list_delete_link (events, l); + break; + } + } + + /* Event got filtered out */ + if (event == NULL) + return NULL; + + fail_unless (event->type == GST_EVENT_TAG); + gst_event_parse_tag (event, &tag_list); + + /* The element is supposed to filter out ReplayGain related tags. */ + fail_if (gst_tag_list_get_double (tag_list, GST_TAG_TRACK_GAIN, &dummy), + "tag event still contains track gain tag"); + fail_if (gst_tag_list_get_double (tag_list, GST_TAG_TRACK_PEAK, &dummy), + "tag event still contains track peak tag"); + fail_if (gst_tag_list_get_double (tag_list, GST_TAG_ALBUM_GAIN, &dummy), + "tag event still contains album gain tag"); + fail_if (gst_tag_list_get_double (tag_list, GST_TAG_ALBUM_PEAK, &dummy), + "tag event still contains album peak tag"); + + return event; +} + +static GstBuffer * +test_buffer_new (gfloat value) +{ + GstBuffer *buf; + GstMapInfo map; + gfloat *data; + gint i; + + buf = gst_buffer_new_and_alloc (8 * sizeof (gfloat)); + gst_buffer_map (buf, &map, GST_MAP_WRITE); + data = (gfloat *) map.data; + for (i = 0; i < 8; i++) + data[i] = value; + gst_buffer_unmap (buf, &map); + + + ASSERT_BUFFER_REFCOUNT (buf, "buf", 1); + + return buf; +} + +#define MATCH_GAIN(g1, g2) ((g1 < g2 + 1e-6) && (g2 < g1 + 1e-6)) + +static void +fail_unless_target_gain (GstElement * element, gdouble expected_gain) +{ + gdouble prop_gain; + + g_object_get (element, "target-gain", &prop_gain, NULL); + + fail_unless (MATCH_GAIN (prop_gain, expected_gain), + "Target gain is %.2f dB, expected %.2f dB", prop_gain, expected_gain); +} + +static void +fail_unless_result_gain (GstElement * element, gdouble expected_gain) +{ + GstBuffer *input_buf, *output_buf; + gfloat *data; + gfloat input_sample, output_sample; + gdouble gain, prop_gain; + gboolean is_passthrough, expect_passthrough; + gint i; + GstMapInfo map; + + fail_unless (g_list_length (buffers) == 0); + + input_sample = 1.0; + input_buf = test_buffer_new (input_sample); + + /* We keep an extra reference to detect passthrough mode. */ + gst_buffer_ref (input_buf); + /* Pushing steals a reference. */ + fail_unless (gst_pad_push (mysrcpad, input_buf) == GST_FLOW_OK); + gst_buffer_unref (input_buf); + + /* The output buffer ends up on the global buffer list. */ + fail_unless (g_list_length (buffers) == 1); + output_buf = buffers->data; + fail_if (output_buf == NULL); + + buffers = g_list_remove (buffers, output_buf); + ASSERT_BUFFER_REFCOUNT (output_buf, "output_buf", 1); + + fail_unless_equals_int (gst_buffer_get_size (output_buf), + 8 * sizeof (gfloat)); + + gst_buffer_map (output_buf, &map, GST_MAP_READ); + data = (gfloat *) map.data; + + output_sample = *data; + fail_if (output_sample == 0.0, "First output sample is zero"); + for (i = 1; i < 8; i++) { + fail_unless (output_sample == data[i], "Output samples not uniform"); + }; + gst_buffer_unmap (output_buf, &map); + + gain = 20. * log10 (output_sample / input_sample); + fail_unless (MATCH_GAIN (gain, expected_gain), + "Applied gain is %.2f dB, expected %.2f dB", gain, expected_gain); + g_object_get (element, "result-gain", &prop_gain, NULL); + fail_unless (MATCH_GAIN (prop_gain, expected_gain), + "Result gain is %.2f dB, expected %.2f dB", prop_gain, expected_gain); + + is_passthrough = (output_buf == input_buf); + expect_passthrough = MATCH_GAIN (expected_gain, +0.00); + fail_unless (is_passthrough == expect_passthrough, + expect_passthrough + ? "Expected operation in passthrough mode" + : "Incorrect passthrough behaviour"); + + gst_buffer_unref (output_buf); +} + +static void +fail_unless_gain (GstElement * element, gdouble expected_gain) +{ + fail_unless_target_gain (element, expected_gain); + fail_unless_result_gain (element, expected_gain); +} + +/* Start of tests. */ + +GST_START_TEST (test_no_buffer) +{ + GstElement *element = setup_rgvolume (); + + set_playing_state (element); + set_null_state (element); + set_playing_state (element); + send_eos_event (element); + + cleanup_rgvolume (element); +} + +GST_END_TEST; + +GST_START_TEST (test_events) +{ + GstElement *element = setup_rgvolume (); + GstEvent *event; + GstEvent *new_event; + GstTagList *tag_list; + gchar *artist; + + set_playing_state (element); + send_stream_start_event (element); + send_caps_event (element); + send_segment_event (element); + + send_empty_buffer (); + + tag_list = gst_tag_list_new_empty (); + gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, + GST_TAG_TRACK_GAIN, +4.95, GST_TAG_TRACK_PEAK, 0.59463, + GST_TAG_ALBUM_GAIN, -1.54, GST_TAG_ALBUM_PEAK, 0.693415, + GST_TAG_ARTIST, "Foobar", NULL); + event = gst_event_new_tag (tag_list); + new_event = send_tag_event (element, event); + gst_event_parse_tag (new_event, &tag_list); + fail_unless (gst_tag_list_get_string (tag_list, GST_TAG_ARTIST, &artist)); + fail_unless (g_str_equal (artist, "Foobar")); + g_free (artist); + gst_event_unref (new_event); + + /* Same as above, but with a non-writable event. */ + + tag_list = gst_tag_list_new_empty (); + gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, + GST_TAG_TRACK_GAIN, +4.95, GST_TAG_TRACK_PEAK, 0.59463, + GST_TAG_ALBUM_GAIN, -1.54, GST_TAG_ALBUM_PEAK, 0.693415, + GST_TAG_ARTIST, "Foobar", NULL); + event = gst_event_new_tag (tag_list); + new_event = send_tag_event (element, event); + gst_event_parse_tag (new_event, &tag_list); + fail_unless (gst_tag_list_get_string (tag_list, GST_TAG_ARTIST, &artist)); + fail_unless (g_str_equal (artist, "Foobar")); + g_free (artist); + gst_event_unref (new_event); + + cleanup_rgvolume (element); +} + +GST_END_TEST; + +GST_START_TEST (test_simple) +{ + GstElement *element = setup_rgvolume (); + GstTagList *tag_list; + + g_object_set (element, "album-mode", FALSE, "headroom", +0.00, + "pre-amp", -6.00, "fallback-gain", +1.23, NULL); + set_playing_state (element); + send_stream_start_event (element); + send_caps_event (element); + send_segment_event (element); + + send_empty_buffer (); + + tag_list = gst_tag_list_new_empty (); + gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, + GST_TAG_TRACK_GAIN, -3.45, GST_TAG_TRACK_PEAK, 1.0, + GST_TAG_ALBUM_GAIN, +2.09, GST_TAG_ALBUM_PEAK, 1.0, NULL); + fail_unless (send_tag_event (element, gst_event_new_tag (tag_list)) == NULL); + fail_unless_gain (element, -9.45); /* pre-amp + track gain */ + send_eos_event (element); + + g_object_set (element, "album-mode", TRUE, NULL); + + send_flush_events (element); + send_segment_event (element); + + tag_list = gst_tag_list_new_empty (); + gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, + GST_TAG_TRACK_GAIN, -3.45, GST_TAG_TRACK_PEAK, 1.0, + GST_TAG_ALBUM_GAIN, +2.09, GST_TAG_ALBUM_PEAK, 1.0, NULL); + fail_unless (send_tag_event (element, gst_event_new_tag (tag_list)) == NULL); + fail_unless_gain (element, -3.91); /* pre-amp + album gain */ + + /* Switching back to track mode in the middle of a stream: */ + g_object_set (element, "album-mode", FALSE, NULL); + fail_unless_gain (element, -9.45); /* pre-amp + track gain */ + send_eos_event (element); + + cleanup_rgvolume (element); +} + +GST_END_TEST; + +/* If there are no gain tags at all, the fallback gain is used. */ + +GST_START_TEST (test_fallback_gain) +{ + GstElement *element = setup_rgvolume (); + GstTagList *tag_list; + + /* First some track where fallback does _not_ apply. */ + + g_object_set (element, "album-mode", FALSE, "headroom", 10.00, + "pre-amp", -6.00, "fallback-gain", -3.00, NULL); + set_playing_state (element); + send_stream_start_event (element); + send_caps_event (element); + send_segment_event (element); + + send_empty_buffer (); + + tag_list = gst_tag_list_new_empty (); + gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, + GST_TAG_TRACK_GAIN, +3.5, GST_TAG_TRACK_PEAK, 1.0, + GST_TAG_ALBUM_GAIN, -0.5, GST_TAG_ALBUM_PEAK, 1.0, NULL); + fail_unless (send_tag_event (element, gst_event_new_tag (tag_list)) == NULL); + fail_unless_gain (element, -2.50); /* pre-amp + track gain */ + send_eos_event (element); + + /* Now a track completely missing tags. */ + send_flush_events (element); + send_segment_event (element); + + fail_unless_gain (element, -9.00); /* pre-amp + fallback-gain */ + + /* Changing the fallback gain in the middle of a stream, going to pass-through + * mode: */ + g_object_set (element, "fallback-gain", +6.00, NULL); + fail_unless_gain (element, +0.00); /* pre-amp + fallback-gain */ + send_eos_event (element); + + /* Verify that result gain is set to +0.00 with pre-amp + fallback-gain > + * +0.00 and no headroom. */ + send_flush_events (element); + send_segment_event (element); + + g_object_set (element, "fallback-gain", +12.00, "headroom", +0.00, NULL); + fail_unless_target_gain (element, +6.00); /* pre-amp + fallback-gain */ + fail_unless_result_gain (element, +0.00); + send_eos_event (element); + + cleanup_rgvolume (element); +} + +GST_END_TEST; + +/* If album gain is to be preferred but not available, the track gain is to be + * taken instead. */ + +GST_START_TEST (test_fallback_track) +{ + GstElement *element = setup_rgvolume (); + GstTagList *tag_list; + + g_object_set (element, "album-mode", TRUE, "headroom", +0.00, + "pre-amp", -6.00, "fallback-gain", +1.23, NULL); + set_playing_state (element); + send_stream_start_event (element); + send_caps_event (element); + send_segment_event (element); + + send_empty_buffer (); + + tag_list = gst_tag_list_new_empty (); + gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, + GST_TAG_TRACK_GAIN, +2.11, GST_TAG_TRACK_PEAK, 1.0, NULL); + fail_unless (send_tag_event (element, gst_event_new_tag (tag_list)) == NULL); + fail_unless_gain (element, -3.89); /* pre-amp + track gain */ + + send_eos_event (element); + + cleanup_rgvolume (element); +} + +GST_END_TEST; + +/* If track gain is to be preferred but not available, the album gain is to be + * taken instead. */ + +GST_START_TEST (test_fallback_album) +{ + GstElement *element = setup_rgvolume (); + GstTagList *tag_list; + + g_object_set (element, "album-mode", FALSE, "headroom", +0.00, + "pre-amp", -6.00, "fallback-gain", +1.23, NULL); + set_playing_state (element); + send_stream_start_event (element); + send_caps_event (element); + send_segment_event (element); + + send_empty_buffer (); + + tag_list = gst_tag_list_new_empty (); + gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, + GST_TAG_ALBUM_GAIN, +3.73, GST_TAG_ALBUM_PEAK, 1.0, NULL); + fail_unless (send_tag_event (element, gst_event_new_tag (tag_list)) == NULL); + fail_unless_gain (element, -2.27); /* pre-amp + album gain */ + + send_eos_event (element); + + cleanup_rgvolume (element); +} + +GST_END_TEST; + +GST_START_TEST (test_headroom) +{ + GstElement *element = setup_rgvolume (); + GstTagList *tag_list; + + g_object_set (element, "album-mode", FALSE, "headroom", +0.00, + "pre-amp", +0.00, "fallback-gain", +1.23, NULL); + set_playing_state (element); + send_stream_start_event (element); + send_caps_event (element); + send_segment_event (element); + + send_empty_buffer (); + + tag_list = gst_tag_list_new_empty (); + gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, + GST_TAG_TRACK_GAIN, +3.50, GST_TAG_TRACK_PEAK, 1.0, NULL); + fail_unless (send_tag_event (element, gst_event_new_tag (tag_list)) == NULL); + fail_unless_target_gain (element, +3.50); /* pre-amp + track gain */ + fail_unless_result_gain (element, +0.00); + send_eos_event (element); + + send_flush_events (element); + send_segment_event (element); + + g_object_set (element, "headroom", +2.00, NULL); + tag_list = gst_tag_list_new_empty (); + gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, + GST_TAG_TRACK_GAIN, +9.18, GST_TAG_TRACK_PEAK, 0.687149, NULL); + fail_unless (send_tag_event (element, gst_event_new_tag (tag_list)) == NULL); + fail_unless_target_gain (element, +9.18); /* pre-amp + track gain */ + /* Result is 20. * log10 (1. / peak) + headroom. */ + fail_unless_result_gain (element, 5.2589816238303335); + send_eos_event (element); + + send_flush_events (element); + send_segment_event (element); + + g_object_set (element, "album-mode", TRUE, NULL); + tag_list = gst_tag_list_new_empty (); + gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, + GST_TAG_ALBUM_GAIN, +5.50, GST_TAG_ALBUM_PEAK, 1.0, NULL); + fail_unless (send_tag_event (element, gst_event_new_tag (tag_list)) == NULL); + fail_unless_target_gain (element, +5.50); /* pre-amp + album gain */ + fail_unless_result_gain (element, +2.00); /* headroom */ + send_eos_event (element); + + cleanup_rgvolume (element); +} + +GST_END_TEST; + +GST_START_TEST (test_reference_level) +{ + GstElement *element = setup_rgvolume (); + GstTagList *tag_list; + + g_object_set (element, + "album-mode", FALSE, + "headroom", +0.00, "pre-amp", +0.00, "fallback-gain", +1.23, NULL); + set_playing_state (element); + + send_stream_start_event (element); + send_caps_event (element); + send_segment_event (element); + + send_empty_buffer (); + + tag_list = gst_tag_list_new_empty (); + gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, + GST_TAG_TRACK_GAIN, 0.00, GST_TAG_TRACK_PEAK, 0.2, + GST_TAG_REFERENCE_LEVEL, 83., NULL); + fail_unless (send_tag_event (element, gst_event_new_tag (tag_list)) == NULL); + /* Because our authorative reference is 89 dB, we bump it up by +6 dB. */ + fail_unless_gain (element, +6.00); /* pre-amp + track gain */ + send_eos_event (element); + + g_object_set (element, "album-mode", TRUE, NULL); + + /* Same as above, but with album gain. */ + send_flush_events (element); + send_segment_event (element); + + tag_list = gst_tag_list_new_empty (); + gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, + GST_TAG_TRACK_GAIN, 1.23, GST_TAG_TRACK_PEAK, 0.1, + GST_TAG_ALBUM_GAIN, 0.00, GST_TAG_ALBUM_PEAK, 0.2, + GST_TAG_REFERENCE_LEVEL, 83., NULL); + fail_unless (send_tag_event (element, gst_event_new_tag (tag_list)) == NULL); + fail_unless_gain (element, +6.00); /* pre-amp + album gain */ + + cleanup_rgvolume (element); +} + +GST_END_TEST; + +static Suite * +rgvolume_suite (void) +{ + Suite *s = suite_create ("rgvolume"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_no_buffer); + tcase_add_test (tc_chain, test_events); + tcase_add_test (tc_chain, test_simple); + tcase_add_test (tc_chain, test_fallback_gain); + tcase_add_test (tc_chain, test_fallback_track); + tcase_add_test (tc_chain, test_fallback_album); + tcase_add_test (tc_chain, test_headroom); + tcase_add_test (tc_chain, test_reference_level); + + return s; +} + +int +main (int argc, char **argv) +{ + gint nf; + + Suite *s = rgvolume_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_ENV); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/rtp-payloading.c b/tests/check/elements/rtp-payloading.c new file mode 100755 index 0000000..a5db021 --- /dev/null +++ b/tests/check/elements/rtp-payloading.c @@ -0,0 +1,970 @@ +/* GStreamer RTP payloader unit tests + * Copyright (C) 2008 Nokia Corporation and its subsidary(-ies) + * contact: <stefan.kost@nokia.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. + */ +#include <gst/check/gstcheck.h> +#include <stdlib.h> +#include <unistd.h> + +#define RELEASE_ELEMENT(x) if(x) {gst_object_unref(x); x = NULL;} + +#define LOOP_COUNT 1 + +/* + * RTP pipeline structure to store the required elements. + */ +typedef struct +{ + GstElement *pipeline; + GstElement *appsrc; + GstElement *rtppay; + GstElement *rtpdepay; + GstElement *fakesink; + const guint8 *frame_data; + int frame_data_size; + int frame_count; +} rtp_pipeline; + +/* + * Number of bytes received in the chain list function when using buffer lists + */ +static guint chain_list_bytes_received; + +/* + * Chain list function for testing buffer lists + */ +static GstFlowReturn +rtp_pipeline_chain_list (GstPad * pad, GstObject * parent, GstBufferList * list) +{ + guint i, len; + + fail_if (!list); + /* + * Count the size of the payload in the buffer list. + */ + len = gst_buffer_list_length (list); + + /* Loop through all groups */ + for (i = 0; i < len; i++) { + GstBuffer *paybuf; + GstMemory *mem; + gint size; + + paybuf = gst_buffer_list_get (list, i); + /* only count real data which is expected in last memory block */ + fail_unless (gst_buffer_n_memory (paybuf) > 1); + mem = gst_buffer_get_memory_range (paybuf, gst_buffer_n_memory (paybuf) - 1, + 1); + size = gst_memory_get_sizes (mem, NULL, NULL); + gst_memory_unref (mem); + chain_list_bytes_received += size; + } + gst_buffer_list_unref (list); + + return GST_FLOW_OK; +} + +static GstFlowReturn +rtp_pipeline_chain (GstPad * pad, GstObject * parent, GstBuffer * buf) +{ + GstBufferList *list; + + list = gst_buffer_list_new_sized (1); + gst_buffer_list_add (list, buf); + return rtp_pipeline_chain_list (pad, parent, list); +} + +/* + * RTP bus callback. + */ +static gboolean +rtp_bus_callback (GstBus * bus, GstMessage * message, gpointer data) +{ + GMainLoop *mainloop = (GMainLoop *) data; + + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ERROR: + { + GError *err; + + gchar *debug; + + gchar *element_name; + + element_name = (message->src) ? gst_object_get_name (message->src) : NULL; + gst_message_parse_error (message, &err, &debug); + g_print ("\nError from element %s: %s\n%s\n\n", + GST_STR_NULL (element_name), err->message, (debug) ? debug : ""); + g_error_free (err); + g_free (debug); + g_free (element_name); + + fail_if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR); + + g_main_loop_quit (mainloop); + } + break; + + case GST_MESSAGE_EOS: + { + g_main_loop_quit (mainloop); + } + break; + break; + + default: + { + } + break; + } + + return TRUE; +} + +/* + * Creates a RTP pipeline for one test. + * @param frame_data Pointer to the frame data which is used to pass thru pay/depayloaders. + * @param frame_data_size Frame data size in bytes. + * @param frame_count Frame count. + * @param filtercaps Caps filters. + * @param pay Payloader name. + * @param depay Depayloader name. + * @return + * Returns pointer to the RTP pipeline. + * The user must free the RTP pipeline when it's not used anymore. + */ +static rtp_pipeline * +rtp_pipeline_create (const guint8 * frame_data, int frame_data_size, + int frame_count, const char *filtercaps, const char *pay, const char *depay) +{ + gchar *pipeline_name; + rtp_pipeline *p; + GstCaps *caps; + + /* Check parameters. */ + if (!frame_data || !pay || !depay) { + return NULL; + } + + /* Allocate memory for the RTP pipeline. */ + p = (rtp_pipeline *) malloc (sizeof (rtp_pipeline)); + + p->frame_data = frame_data; + p->frame_data_size = frame_data_size; + p->frame_count = frame_count; + + /* Create elements. */ + pipeline_name = g_strdup_printf ("%s-%s-pipeline", pay, depay); + p->pipeline = gst_pipeline_new (pipeline_name); + g_free (pipeline_name); + p->appsrc = gst_element_factory_make ("appsrc", NULL); + p->rtppay = gst_element_factory_make (pay, NULL); + p->rtpdepay = gst_element_factory_make (depay, NULL); + p->fakesink = gst_element_factory_make ("fakesink", NULL); + + /* One or more elements are not created successfully or failed to create p? */ + if (!p->pipeline || !p->appsrc || !p->rtppay || !p->rtpdepay || !p->fakesink) { + /* Release created elements. */ + RELEASE_ELEMENT (p->pipeline); + RELEASE_ELEMENT (p->appsrc); + RELEASE_ELEMENT (p->rtppay); + RELEASE_ELEMENT (p->rtpdepay); + RELEASE_ELEMENT (p->fakesink); + + /* Release allocated memory. */ + free (p); + + return NULL; + } + + /* Set src properties. */ + caps = gst_caps_from_string (filtercaps); + g_object_set (p->appsrc, "do-timestamp", TRUE, "caps", caps, + "format", GST_FORMAT_TIME, NULL); + gst_caps_unref (caps); + + /* Add elements to the pipeline. */ + gst_bin_add (GST_BIN (p->pipeline), p->appsrc); + gst_bin_add (GST_BIN (p->pipeline), p->rtppay); + gst_bin_add (GST_BIN (p->pipeline), p->rtpdepay); + gst_bin_add (GST_BIN (p->pipeline), p->fakesink); + + /* Link elements. */ + gst_element_link (p->appsrc, p->rtppay); + gst_element_link (p->rtppay, p->rtpdepay); + gst_element_link (p->rtpdepay, p->fakesink); + + return p; +} + +/* + * Destroys the RTP pipeline. + * @param p Pointer to the RTP pipeline. + */ +static void +rtp_pipeline_destroy (rtp_pipeline * p) +{ + /* Check parameters. */ + if (p == NULL) { + return; + } + + /* Release pipeline. */ + RELEASE_ELEMENT (p->pipeline); + + /* Release allocated memory. */ + free (p); +} + +/* + * Runs the RTP pipeline. + * @param p Pointer to the RTP pipeline. + */ +static void +rtp_pipeline_run (rtp_pipeline * p) +{ + GstFlowReturn flow_ret; + GMainLoop *mainloop = NULL; + GstBus *bus; + gint i, j; + + /* Check parameters. */ + if (p == NULL) { + return; + } + + /* Create mainloop. */ + mainloop = g_main_loop_new (NULL, FALSE); + if (!mainloop) { + return; + } + + /* Add bus callback. */ + bus = gst_pipeline_get_bus (GST_PIPELINE (p->pipeline)); + + gst_bus_add_watch (bus, rtp_bus_callback, (gpointer) mainloop); + gst_object_unref (bus); + + /* Set pipeline to PLAYING. */ + gst_element_set_state (p->pipeline, GST_STATE_PLAYING); + + /* Push data into the pipeline */ + for (i = 0; i < LOOP_COUNT; i++) { + const guint8 *data = p->frame_data; + + for (j = 0; j < p->frame_count; j++) { + GstBuffer *buf; + + buf = + gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY, + (guint8 *) data, p->frame_data_size, 0, p->frame_data_size, NULL, + NULL); + + g_signal_emit_by_name (p->appsrc, "push-buffer", buf, &flow_ret); + fail_unless_equals_int (flow_ret, GST_FLOW_OK); + data += p->frame_data_size; + + gst_buffer_unref (buf); + } + } + + g_signal_emit_by_name (p->appsrc, "end-of-stream", &flow_ret); + + /* Run mainloop. */ + g_main_loop_run (mainloop); + + /* Set pipeline to NULL. */ + gst_element_set_state (p->pipeline, GST_STATE_NULL); + + /* Release mainloop. */ + g_main_loop_unref (mainloop); +} + +/* + * Enables buffer lists and adds a chain_list_function to the depayloader. + * @param p Pointer to the RTP pipeline. + */ +static void +rtp_pipeline_enable_lists (rtp_pipeline * p, guint mtu_size) +{ + GstPad *pad; + + /* set mtu size if needed */ + if (mtu_size) { + g_object_set (p->rtppay, "mtu", mtu_size, NULL); + } + + /* Add chain list function for the buffer list tests */ + pad = gst_element_get_static_pad (p->rtpdepay, "sink"); + gst_pad_set_chain_list_function (pad, + GST_DEBUG_FUNCPTR (rtp_pipeline_chain_list)); + /* .. to satisfy this silly test code in case someone dares push a buffer */ + gst_pad_set_chain_function (pad, GST_DEBUG_FUNCPTR (rtp_pipeline_chain)); + gst_object_unref (pad); +} + +/* + * Creates the RTP pipeline and runs the test using the pipeline. + * @param frame_data Pointer to the frame data which is used to pass thru pay/depayloaders. + * @param frame_data_size Frame data size in bytes. + * @param frame_count Frame count. + * @param filtercaps Caps filters. + * @param pay Payloader name. + * @param depay Depayloader name. + * @bytes_sent bytes that will be sent, used when testing buffer lists + * @mtu_size set mtu size when testing lists + * @use_lists enable buffer lists + */ +static void +rtp_pipeline_test (const guint8 * frame_data, int frame_data_size, + int frame_count, const char *filtercaps, const char *pay, const char *depay, + guint bytes_sent, guint mtu_size, gboolean use_lists) +{ + /* Create RTP pipeline. */ + rtp_pipeline *p = + rtp_pipeline_create (frame_data, frame_data_size, frame_count, filtercaps, + pay, depay); + + if (p == NULL) { + return; + } + + if (use_lists) { + rtp_pipeline_enable_lists (p, mtu_size); + chain_list_bytes_received = 0; + } + + /* Run RTP pipeline. */ + rtp_pipeline_run (p); + + /* Destroy RTP pipeline. */ + rtp_pipeline_destroy (p); + + if (use_lists) { + /* 'next NAL' indicator is 4 bytes */ + fail_if (chain_list_bytes_received != bytes_sent * LOOP_COUNT); + } +} + +static const guint8 rtp_ilbc_frame_data[] = + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static int rtp_ilbc_frame_data_size = 20; + +static int rtp_ilbc_frame_count = 1; + +GST_START_TEST (rtp_ilbc) +{ + rtp_pipeline_test (rtp_ilbc_frame_data, rtp_ilbc_frame_data_size, + rtp_ilbc_frame_count, "audio/x-iLBC,mode=20", "rtpilbcpay", + "rtpilbcdepay", 0, 0, FALSE); +} + +GST_END_TEST; +static const guint8 rtp_gsm_frame_data[] = + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static int rtp_gsm_frame_data_size = 20; + +static int rtp_gsm_frame_count = 1; + +GST_START_TEST (rtp_gsm) +{ + rtp_pipeline_test (rtp_gsm_frame_data, rtp_gsm_frame_data_size, + rtp_gsm_frame_count, "audio/x-gsm,rate=8000,channels=1", "rtpgsmpay", + "rtpgsmdepay", 0, 0, FALSE); +} + +GST_END_TEST; +static const guint8 rtp_amr_frame_data[] = + { 0x3c, 0x24, 0x03, 0xb3, 0x48, 0x10, 0x68, 0x46, 0x6c, 0xec, 0x03, + 0x7a, 0x37, 0x16, 0x41, 0x41, 0xc0, 0x00, 0x0d, 0xcd, 0x12, 0xed, + 0xad, 0x80, 0x00, 0x00, 0x11, 0x31, 0x00, 0x00, 0x0d, 0xa0 +}; + +static int rtp_amr_frame_data_size = 32; + +static int rtp_amr_frame_count = 1; + +GST_START_TEST (rtp_amr) +{ + rtp_pipeline_test (rtp_amr_frame_data, rtp_amr_frame_data_size, + rtp_amr_frame_count, "audio/AMR,channels=1,rate=8000", "rtpamrpay", + "rtpamrdepay", 0, 0, FALSE); +} + +GST_END_TEST; +static const guint8 rtp_pcma_frame_data[] = + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static int rtp_pcma_frame_data_size = 20; + +static int rtp_pcma_frame_count = 1; + +GST_START_TEST (rtp_pcma) +{ + rtp_pipeline_test (rtp_pcma_frame_data, rtp_pcma_frame_data_size, + rtp_pcma_frame_count, "audio/x-alaw,channels=1,rate=8000", "rtppcmapay", + "rtppcmadepay", 0, 0, FALSE); +} + +GST_END_TEST; +static const guint8 rtp_pcmu_frame_data[] = + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static int rtp_pcmu_frame_data_size = 20; + +static int rtp_pcmu_frame_count = 1; + +GST_START_TEST (rtp_pcmu) +{ + rtp_pipeline_test (rtp_pcmu_frame_data, rtp_pcmu_frame_data_size, + rtp_pcmu_frame_count, "audio/x-mulaw,channels=1,rate=8000", "rtppcmupay", + "rtppcmudepay", 0, 0, FALSE); +} + +GST_END_TEST; +static const guint8 rtp_mpa_frame_data[] = + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static int rtp_mpa_frame_data_size = 20; + +static int rtp_mpa_frame_count = 1; + +GST_START_TEST (rtp_mpa) +{ + rtp_pipeline_test (rtp_mpa_frame_data, rtp_mpa_frame_data_size, + rtp_mpa_frame_count, "audio/mpeg,mpegversion=1", "rtpmpapay", + "rtpmpadepay", 0, 0, FALSE); +} + +GST_END_TEST; +static const guint8 rtp_h263_frame_data[] = + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static int rtp_h263_frame_data_size = 20; + +static int rtp_h263_frame_count = 1; + +GST_START_TEST (rtp_h263) +{ + rtp_pipeline_test (rtp_h263_frame_data, rtp_h263_frame_data_size, + rtp_h263_frame_count, "video/x-h263,variant=(string)itu,h263version=h263", + "rtph263pay", "rtph263depay", 0, 0, FALSE); +} + +GST_END_TEST; +static const guint8 rtp_h263p_frame_data[] = + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static int rtp_h263p_frame_data_size = 20; + +static int rtp_h263p_frame_count = 1; + +GST_START_TEST (rtp_h263p) +{ + rtp_pipeline_test (rtp_h263p_frame_data, rtp_h263p_frame_data_size, + rtp_h263p_frame_count, "video/x-h263,variant=(string)itu," + "h263version=(string)h263", "rtph263ppay", "rtph263pdepay", 0, 0, FALSE); + + /* payloader should accept any input that matches the template caps + * if there's just a udpsink or fakesink downstream */ + rtp_pipeline_test (rtp_h263p_frame_data, rtp_h263p_frame_data_size, + rtp_h263p_frame_count, "video/x-h263,variant=(string)itu," + "h263version=(string)h263", "rtph263ppay", "identity", 0, 0, FALSE); + + /* default output of avenc_h263p */ + rtp_pipeline_test (rtp_h263p_frame_data, rtp_h263p_frame_data_size, + rtp_h263p_frame_count, "video/x-h263,variant=(string)itu," + "h263version=(string)h263p, annex-f=(boolean)true, " + "annex-j=(boolean)true, annex-i=(boolean)true, annex-t=(boolean)true", + "rtph263ppay", "identity", 0, 0, FALSE); + + /* pay ! depay should also work with any input */ + rtp_pipeline_test (rtp_h263p_frame_data, rtp_h263p_frame_data_size, + rtp_h263p_frame_count, "video/x-h263,variant=(string)itu," + "h263version=(string)h263p, annex-f=(boolean)true, " + "annex-j=(boolean)true, annex-i=(boolean)true, annex-t=(boolean)true", + "rtph263ppay", "rtph263pdepay", 0, 0, FALSE); +} + +GST_END_TEST; +static const guint8 rtp_h264_frame_data[] = + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static int rtp_h264_frame_data_size = 20; + +static int rtp_h264_frame_count = 1; + +GST_START_TEST (rtp_h264) +{ + /* FIXME 0.11: fully specify h264 caps (and make payloader check) */ + rtp_pipeline_test (rtp_h264_frame_data, rtp_h264_frame_data_size, + rtp_h264_frame_count, + "video/x-h264,stream-format=(string)byte-stream,alignment=(string)nal", + "rtph264pay", "rtph264depay", 0, 0, FALSE); +} + +GST_END_TEST; +static const guint8 rtp_h264_list_lt_mtu_frame_data[] = + /* not packetized, next NAL starts with 0001 */ +{ 0x00, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0xad, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x10 +}; + +static int rtp_h264_list_lt_mtu_frame_data_size = 16; + +static int rtp_h264_list_lt_mtu_frame_count = 2; + +/* NAL = 4 bytes */ +/* also 2 bytes FU-A header each time */ +static int rtp_h264_list_lt_mtu_bytes_sent = 2 * (16 - 4); + +static int rtp_h264_list_lt_mtu_mtu_size = 1024; + +GST_START_TEST (rtp_h264_list_lt_mtu) +{ + /* FIXME 0.11: fully specify h264 caps (and make payloader check) */ + rtp_pipeline_test (rtp_h264_list_lt_mtu_frame_data, + rtp_h264_list_lt_mtu_frame_data_size, rtp_h264_list_lt_mtu_frame_count, + "video/x-h264,stream-format=(string)byte-stream,alignment=(string)nal", + "rtph264pay", "rtph264depay", + rtp_h264_list_lt_mtu_bytes_sent, rtp_h264_list_lt_mtu_mtu_size, TRUE); +} + +GST_END_TEST; +static const guint8 rtp_h264_list_lt_mtu_frame_data_avc[] = + /* packetized data */ +{ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, + 0xad, 0x80, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x0d, 0x00 +}; + +/* NAL = 4 bytes */ +static int rtp_h264_list_lt_mtu_bytes_sent_avc = 2 * (16 - 2 * 4); + +//static int rtp_h264_list_lt_mtu_mtu_size = 1024; + +GST_START_TEST (rtp_h264_list_lt_mtu_avc) +{ + /* FIXME 0.11: fully specify h264 caps (and make payloader check) */ + rtp_pipeline_test (rtp_h264_list_lt_mtu_frame_data_avc, + rtp_h264_list_lt_mtu_frame_data_size, rtp_h264_list_lt_mtu_frame_count, + "video/x-h264,stream-format=(string)avc,alignment=(string)au," + "codec_data=(buffer)01640014ffe1001867640014acd94141fb0110000003001773594000f142996001000568ebecb22c", + "rtph264pay", "rtph264depay", + rtp_h264_list_lt_mtu_bytes_sent_avc, rtp_h264_list_lt_mtu_mtu_size, TRUE); +} + +GST_END_TEST; +static const guint8 rtp_h264_list_gt_mtu_frame_data[] = + /* not packetized, next NAL starts with 0001 */ +{ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 +}; + +static int rtp_h264_list_gt_mtu_frame_data_size = 64; + +static int rtp_h264_list_gt_mtu_frame_count = 1; + +/* NAL = 4 bytes. When data does not fit into 1 mtu, 1 byte will be skipped */ +static int rtp_h264_list_gt_mtu_bytes_sent = 1 * (64 - 4) - 1; + +static int rtp_h264_list_gt_mtu_mty_size = 28; + +GST_START_TEST (rtp_h264_list_gt_mtu) +{ + /* FIXME 0.11: fully specify h264 caps (and make payloader check) */ + rtp_pipeline_test (rtp_h264_list_gt_mtu_frame_data, + rtp_h264_list_gt_mtu_frame_data_size, rtp_h264_list_gt_mtu_frame_count, + "video/x-h264,stream-format=(string)byte-stream,alignment=(string)nal", + "rtph264pay", "rtph264depay", + rtp_h264_list_gt_mtu_bytes_sent, rtp_h264_list_gt_mtu_mty_size, TRUE); +} + +GST_END_TEST; +static const guint8 rtp_h264_list_gt_mtu_frame_data_avc[] = + /* packetized data */ +{ 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/* NAL = 4 bytes. When data does not fit into 1 mtu, 1 byte will be skipped */ +static int rtp_h264_list_gt_mtu_bytes_sent_avc = 1 * (64 - 2 * 4 - 2 * 1); + +GST_START_TEST (rtp_h264_list_gt_mtu_avc) +{ + /* FIXME 0.11: fully specify h264 caps (and make payloader check) */ + rtp_pipeline_test (rtp_h264_list_gt_mtu_frame_data_avc, + rtp_h264_list_gt_mtu_frame_data_size, rtp_h264_list_gt_mtu_frame_count, + "video/x-h264,stream-format=(string)avc,alignment=(string)au," + "codec_data=(buffer)01640014ffe1001867640014acd94141fb0110000003001773594000f142996001000568ebecb22c", + "rtph264pay", "rtph264depay", + rtp_h264_list_gt_mtu_bytes_sent_avc, rtp_h264_list_gt_mtu_mty_size, TRUE); +} + +GST_END_TEST; + +static const guint8 rtp_L16_frame_data[] = + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static int rtp_L16_frame_data_size = 20; + +static int rtp_L16_frame_count = 1; + +GST_START_TEST (rtp_L16) +{ + rtp_pipeline_test (rtp_L16_frame_data, rtp_L16_frame_data_size, + rtp_L16_frame_count, + "audio/x-raw,format=S16BE,rate=1,channels=1,layout=(string)interleaved", + "rtpL16pay", "rtpL16depay", 0, 0, FALSE); +} + +GST_END_TEST; + +static const guint8 rtp_L24_frame_data[] = + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static int rtp_L24_frame_data_size = 24; + +static int rtp_L24_frame_count = 1; + +GST_START_TEST (rtp_L24) +{ + rtp_pipeline_test (rtp_L24_frame_data, rtp_L24_frame_data_size, + rtp_L24_frame_count, + "audio/x-raw,format=S24BE,rate=1,channels=1,layout=(string)interleaved", + "rtpL24pay", "rtpL24depay", 0, 0, FALSE); +} + +GST_END_TEST; +static const guint8 rtp_mp2t_frame_data[] = + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static int rtp_mp2t_frame_data_size = 20; + +static int rtp_mp2t_frame_count = 1; + +GST_START_TEST (rtp_mp2t) +{ + rtp_pipeline_test (rtp_mp2t_frame_data, rtp_mp2t_frame_data_size, + rtp_mp2t_frame_count, "video/mpegts,packetsize=188,systemstream=true", + "rtpmp2tpay", "rtpmp2tdepay", 0, 0, FALSE); +} + +GST_END_TEST; +static const guint8 rtp_mp4v_frame_data[] = + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static int rtp_mp4v_frame_data_size = 20; + +static int rtp_mp4v_frame_count = 1; + +GST_START_TEST (rtp_mp4v) +{ + rtp_pipeline_test (rtp_mp4v_frame_data, rtp_mp4v_frame_data_size, + rtp_mp4v_frame_count, "video/mpeg,mpegversion=4,systemstream=false", + "rtpmp4vpay", "rtpmp4vdepay", 0, 0, FALSE); +} + +GST_END_TEST; +static const guint8 rtp_mp4v_list_frame_data[] = + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static int rtp_mp4v_list_frame_data_size = 20; + +static int rtp_mp4v_list_frame_count = 1; + +static int rtp_mp4v_list_bytes_sent = 1 * 20; + +GST_START_TEST (rtp_mp4v_list) +{ + rtp_pipeline_test (rtp_mp4v_list_frame_data, rtp_mp4v_list_frame_data_size, + rtp_mp4v_list_frame_count, + "video/mpeg,mpegversion=4,systemstream=false,codec_data=(buffer)000001b001", + "rtpmp4vpay", "rtpmp4vdepay", rtp_mp4v_list_bytes_sent, 0, TRUE); +} + +GST_END_TEST; +static const guint8 rtp_mp4g_frame_data[] = + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static int rtp_mp4g_frame_data_size = 20; + +static int rtp_mp4g_frame_count = 1; + +GST_START_TEST (rtp_mp4g) +{ + rtp_pipeline_test (rtp_mp4g_frame_data, rtp_mp4g_frame_data_size, + rtp_mp4g_frame_count, + "video/mpeg,mpegversion=4,systemstream=false,codec_data=(buffer)000001b001", + "rtpmp4gpay", "rtpmp4gdepay", 0, 0, FALSE); +} + +GST_END_TEST; +static const guint8 rtp_theora_frame_data[] = + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static int rtp_theora_frame_data_size = 20; + +static int rtp_theora_frame_count = 1; + +GST_START_TEST (rtp_theora) +{ + rtp_pipeline_test (rtp_theora_frame_data, rtp_theora_frame_data_size, + rtp_theora_frame_count, "video/x-theora", "rtptheorapay", + "rtptheoradepay", 0, 0, FALSE); +} + +GST_END_TEST; +static const guint8 rtp_vorbis_frame_data[] = + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static int rtp_vorbis_frame_data_size = 20; + +static int rtp_vorbis_frame_count = 1; + +GST_START_TEST (rtp_vorbis) +{ + rtp_pipeline_test (rtp_vorbis_frame_data, rtp_vorbis_frame_data_size, + rtp_vorbis_frame_count, "audio/x-vorbis", "rtpvorbispay", + "rtpvorbisdepay", 0, 0, FALSE); +} + +GST_END_TEST; +static const guint8 rtp_jpeg_frame_data[] = + { /* SOF */ 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x08, 0x00, 0x08, + 0x03, 0x00, 0x21, 0x08, 0x01, 0x11, 0x08, 0x02, 0x11, 0x08, + /* DQT */ 0xFF, 0xDB, 0x00, 0x43, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* DATA */ 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static int rtp_jpeg_frame_data_size = sizeof (rtp_jpeg_frame_data); + +static int rtp_jpeg_frame_count = 1; + +GST_START_TEST (rtp_jpeg) +{ + rtp_pipeline_test (rtp_jpeg_frame_data, rtp_jpeg_frame_data_size, + rtp_jpeg_frame_count, "video/x-jpeg,height=640,width=480", "rtpjpegpay", + "rtpjpegdepay", 0, 0, FALSE); +} + +GST_END_TEST; + +GST_START_TEST (rtp_jpeg_width_greater_than_2040) +{ + rtp_pipeline_test (rtp_jpeg_frame_data, rtp_jpeg_frame_data_size, + rtp_jpeg_frame_count, "video/x-jpeg,height=2048,width=480", "rtpjpegpay", + "rtpjpegdepay", 0, 0, FALSE); +} + +GST_END_TEST; + +GST_START_TEST (rtp_jpeg_height_greater_than_2040) +{ + rtp_pipeline_test (rtp_jpeg_frame_data, rtp_jpeg_frame_data_size, + rtp_jpeg_frame_count, "video/x-jpeg,height=640,width=2048", "rtpjpegpay", + "rtpjpegdepay", 0, 0, FALSE); +} + +GST_END_TEST; + +GST_START_TEST (rtp_jpeg_width_and_height_greater_than_2040) +{ + rtp_pipeline_test (rtp_jpeg_frame_data, rtp_jpeg_frame_data_size, + rtp_jpeg_frame_count, "video/x-jpeg,height=2048,width=2048", "rtpjpegpay", + "rtpjpegdepay", 0, 0, FALSE); +} + +GST_END_TEST; + +static const guint8 rtp_jpeg_list_frame_data[] = + { /* SOF */ 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x08, 0x00, 0x08, + 0x03, 0x00, 0x21, 0x08, 0x01, 0x11, 0x08, 0x02, 0x11, 0x08, + /* DQT */ 0xFF, 0xDB, 0x00, 0x43, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* DATA */ 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static int rtp_jpeg_list_frame_data_size = sizeof (rtp_jpeg_list_frame_data); + +static int rtp_jpeg_list_frame_count = 1; + +static int rtp_jpeg_list_bytes_sent = 1 * sizeof (rtp_jpeg_list_frame_data); + +GST_START_TEST (rtp_jpeg_list) +{ + rtp_pipeline_test (rtp_jpeg_list_frame_data, rtp_jpeg_list_frame_data_size, + rtp_jpeg_list_frame_count, "video/x-jpeg,height=640,width=480", + "rtpjpegpay", "rtpjpegdepay", rtp_jpeg_list_bytes_sent, 0, TRUE); +} + +GST_END_TEST; + +GST_START_TEST (rtp_jpeg_list_width_greater_than_2040) +{ + rtp_pipeline_test (rtp_jpeg_list_frame_data, rtp_jpeg_list_frame_data_size, + rtp_jpeg_list_frame_count, "video/x-jpeg,height=2048,width=480", + "rtpjpegpay", "rtpjpegdepay", rtp_jpeg_list_bytes_sent, 0, TRUE); +} + +GST_END_TEST; + +GST_START_TEST (rtp_jpeg_list_height_greater_than_2040) +{ + rtp_pipeline_test (rtp_jpeg_list_frame_data, rtp_jpeg_list_frame_data_size, + rtp_jpeg_list_frame_count, "video/x-jpeg,height=640,width=2048", + "rtpjpegpay", "rtpjpegdepay", rtp_jpeg_list_bytes_sent, 0, TRUE); +} + +GST_END_TEST; + +GST_START_TEST (rtp_jpeg_list_width_and_height_greater_than_2040) +{ + rtp_pipeline_test (rtp_jpeg_list_frame_data, rtp_jpeg_list_frame_data_size, + rtp_jpeg_list_frame_count, "video/x-jpeg,height=2048,width=2048", + "rtpjpegpay", "rtpjpegdepay", rtp_jpeg_list_bytes_sent, 0, TRUE); +} + +GST_END_TEST; + +static const guint8 rtp_g729_frame_data[] = + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static int rtp_g729_frame_data_size = 22; + +static int rtp_g729_frame_count = 1; + +GST_START_TEST (rtp_g729) +{ + rtp_pipeline_test (rtp_g729_frame_data, rtp_g729_frame_data_size, + rtp_g729_frame_count, "audio/G729,rate=8000,channels=1", "rtpg729pay", + "rtpg729depay", 0, 0, FALSE); +} + +GST_END_TEST; + +/* + * Creates the test suite. + * + * Returns: pointer to the test suite. + */ +static Suite * +rtp_payloading_suite (void) +{ + Suite *s = suite_create ("rtp_data_test"); + + TCase *tc_chain = tcase_create ("linear"); + + /* Set timeout to 60 seconds. */ + tcase_set_timeout (tc_chain, 60); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, rtp_ilbc); + tcase_add_test (tc_chain, rtp_gsm); + tcase_add_test (tc_chain, rtp_amr); + tcase_add_test (tc_chain, rtp_pcma); + tcase_add_test (tc_chain, rtp_pcmu); + tcase_add_test (tc_chain, rtp_mpa); + tcase_add_test (tc_chain, rtp_h263); + tcase_add_test (tc_chain, rtp_h263p); + tcase_add_test (tc_chain, rtp_h264); + tcase_add_test (tc_chain, rtp_h264_list_lt_mtu); + tcase_add_test (tc_chain, rtp_h264_list_lt_mtu_avc); + tcase_add_test (tc_chain, rtp_h264_list_gt_mtu); + tcase_add_test (tc_chain, rtp_h264_list_gt_mtu_avc); + tcase_add_test (tc_chain, rtp_L16); + tcase_add_test (tc_chain, rtp_L24); + tcase_add_test (tc_chain, rtp_mp2t); + tcase_add_test (tc_chain, rtp_mp4v); + tcase_add_test (tc_chain, rtp_mp4v_list); + tcase_add_test (tc_chain, rtp_mp4g); + tcase_add_test (tc_chain, rtp_theora); + tcase_add_test (tc_chain, rtp_vorbis); + tcase_add_test (tc_chain, rtp_jpeg); + tcase_add_test (tc_chain, rtp_jpeg_width_greater_than_2040); + tcase_add_test (tc_chain, rtp_jpeg_height_greater_than_2040); + tcase_add_test (tc_chain, rtp_jpeg_width_and_height_greater_than_2040); + tcase_add_test (tc_chain, rtp_jpeg_list); + tcase_add_test (tc_chain, rtp_jpeg_list_width_greater_than_2040); + tcase_add_test (tc_chain, rtp_jpeg_list_height_greater_than_2040); + tcase_add_test (tc_chain, rtp_jpeg_list_width_and_height_greater_than_2040); + tcase_add_test (tc_chain, rtp_g729); + return s; +} + +GST_CHECK_MAIN (rtp_payloading) diff --git a/tests/check/elements/rtpaux.c b/tests/check/elements/rtpaux.c new file mode 100755 index 0000000..1f410bf --- /dev/null +++ b/tests/check/elements/rtpaux.c @@ -0,0 +1,418 @@ +/* GStreamer + * + * Copyright (C) 2013 Collabora Ltd. + * @author Julien Isorce <julien.isorce@collabora.co.uk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <gst/check/gstcheck.h> +#include <gst/check/gstconsistencychecker.h> +#include <gst/check/gsttestclock.h> +#include <gst/rtp/gstrtpbuffer.h> + +static gboolean send_pipeline_eos = FALSE; +static gboolean receive_pipeline_eos = FALSE; + +static void +message_received (GstBus * bus, GstMessage * message, GstPipeline * bin) +{ + GST_INFO ("bus message from \"%" GST_PTR_FORMAT "\": %" GST_PTR_FORMAT, + GST_MESSAGE_SRC (message), message); + + switch (message->type) { + case GST_MESSAGE_EOS: + if (!strcmp ("pipeline_send", + GST_OBJECT_NAME (GST_MESSAGE_SRC (message)))) + send_pipeline_eos = TRUE; + else if (!strcmp ("pipeline_receive", + GST_OBJECT_NAME (GST_MESSAGE_SRC (message)))) + receive_pipeline_eos = TRUE; + else + fail ("Unknown pipeline: %s", + GST_OBJECT_NAME (GST_MESSAGE_SRC (message))); + break; + case GST_MESSAGE_WARNING:{ + GError *gerror; + gchar *debug; + + gst_message_parse_warning (message, &gerror, &debug); + gst_object_default_error (GST_MESSAGE_SRC (message), gerror, debug); + g_error_free (gerror); + g_free (debug); + break; + } + case GST_MESSAGE_ERROR:{ + GError *gerror; + gchar *debug; + + gst_message_parse_error (message, &gerror, &debug); + gst_object_default_error (GST_MESSAGE_SRC (message), gerror, debug); + g_error_free (gerror); + g_free (debug); + fail ("Error!"); + break; + } + default: + break; + } +} + +typedef struct +{ + guint count; + guint nb_packets; + guint drop_every_n_packets; +} RTXSendData; + +static GstPadProbeReturn +rtprtxsend_srcpad_probe (GstPad * pad, GstPadProbeInfo * info, + gpointer user_data) +{ + GstPadProbeReturn ret = GST_PAD_PROBE_OK; + + if (info->type == (GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_PUSH)) { + GstBuffer *buffer = GST_BUFFER (info->data); + RTXSendData *rtxdata = (RTXSendData *) user_data; + GstRTPBuffer rtp = GST_RTP_BUFFER_INIT; + guint payload_type = 0; + + gst_rtp_buffer_map (buffer, GST_MAP_READ, &rtp); + payload_type = gst_rtp_buffer_get_payload_type (&rtp); + + /* main stream packets */ + if (payload_type == 96) { + /* count packets of the main stream */ + ++rtxdata->nb_packets; + /* drop some packets */ + if (rtxdata->count < rtxdata->drop_every_n_packets) { + ++rtxdata->count; + } else { + /* drop a packet every 'rtxdata->count' packets */ + rtxdata->count = 1; + ret = GST_PAD_PROBE_DROP; + } + } else { + /* retransmission packets */ + } + + gst_rtp_buffer_unmap (&rtp); + } + + return ret; +} + +static void +on_rtpbinreceive_pad_added (GstElement * element, GstPad * newPad, + gpointer data) +{ + GstElement *rtpdepayloader = GST_ELEMENT (data); + + gchar *padName = gst_pad_get_name (newPad); + if (g_str_has_prefix (padName, "recv_rtp_src_")) { + GstPad *sinkpad = gst_element_get_static_pad (rtpdepayloader, "sink"); + gst_pad_link (newPad, sinkpad); + gst_object_unref (sinkpad); + } + g_free (padName); +} + +static gboolean +on_timeout (gpointer data) +{ + GstEvent *eos = gst_event_new_eos (); + if (!gst_element_send_event (GST_ELEMENT (data), eos)) { + GST_ERROR ("failed to send end of stream event"); + gst_event_unref (eos); + } + + return FALSE; +} + +static GstElement * +request_aux_receive (GstElement * rtpbin, guint sessid, GstElement * receive) +{ + GstElement *bin; + GstPad *pad; + + GST_INFO ("creating AUX receiver"); + bin = gst_bin_new (NULL); + gst_bin_add (GST_BIN (bin), receive); + + pad = gst_element_get_static_pad (receive, "src"); + gst_element_add_pad (bin, gst_ghost_pad_new ("src_0", pad)); + gst_object_unref (pad); + pad = gst_element_get_static_pad (receive, "sink"); + gst_element_add_pad (bin, gst_ghost_pad_new ("sink_0", pad)); + gst_object_unref (pad); + + return bin; +} + +static GstElement * +request_aux_send (GstElement * rtpbin, guint sessid, GstElement * send) +{ + GstElement *bin; + GstPad *pad; + + GST_INFO ("creating AUX sender"); + bin = gst_bin_new (NULL); + gst_bin_add (GST_BIN (bin), send); + + pad = gst_element_get_static_pad (send, "src"); + gst_element_add_pad (bin, gst_ghost_pad_new ("src_0", pad)); + gst_object_unref (pad); + pad = gst_element_get_static_pad (send, "sink"); + gst_element_add_pad (bin, gst_ghost_pad_new ("sink_0", pad)); + gst_object_unref (pad); + + return bin; +} + + +GST_START_TEST (test_simple_rtpbin_aux) +{ + GstElement *binsend, *rtpbinsend, *src, *encoder, *rtppayloader, + *rtprtxsend, *sendrtp_udpsink, *sendrtcp_udpsink, *sendrtcp_udpsrc; + GstElement *binreceive, *rtpbinreceive, *recvrtp_udpsrc, *recvrtcp_udpsrc, + *recvrtcp_udpsink, *rtprtxreceive, *rtpdepayloader, *decoder, *converter, + *sink; + GstBus *bussend; + GstBus *busreceive; + gboolean res; + GstCaps *rtpcaps = NULL; + GstStructure *pt_map; + GstStateChangeReturn state_res = GST_STATE_CHANGE_FAILURE; + GstPad *srcpad = NULL; + guint nb_rtx_send_packets = 0; + guint nb_rtx_recv_packets = 0; + RTXSendData send_rtxdata; + send_rtxdata.count = 1; + send_rtxdata.nb_packets = 0; + send_rtxdata.drop_every_n_packets = 50; + + GST_INFO ("preparing test"); + + /* build pipeline */ + binsend = gst_pipeline_new ("pipeline_send"); + bussend = gst_element_get_bus (binsend); + gst_bus_add_signal_watch_full (bussend, G_PRIORITY_HIGH); + + binreceive = gst_pipeline_new ("pipeline_receive"); + busreceive = gst_element_get_bus (binreceive); + gst_bus_add_signal_watch_full (busreceive, G_PRIORITY_HIGH); + + rtpbinsend = gst_element_factory_make ("rtpbin", "rtpbinsend"); + g_object_set (rtpbinsend, "latency", 200, "do-retransmission", TRUE, NULL); + src = gst_element_factory_make ("audiotestsrc", "src"); + encoder = gst_element_factory_make ("speexenc", "encoder"); + rtppayloader = gst_element_factory_make ("rtpspeexpay", "rtppayloader"); + rtprtxsend = gst_element_factory_make ("rtprtxsend", "rtprtxsend"); + sendrtp_udpsink = gst_element_factory_make ("udpsink", "sendrtp_udpsink"); + g_object_set (sendrtp_udpsink, "host", "127.0.0.1", NULL); + g_object_set (sendrtp_udpsink, "port", 5006, NULL); + sendrtcp_udpsink = gst_element_factory_make ("udpsink", "sendrtcp_udpsink"); + g_object_set (sendrtcp_udpsink, "host", "127.0.0.1", NULL); + g_object_set (sendrtcp_udpsink, "port", 5007, NULL); + g_object_set (sendrtcp_udpsink, "sync", FALSE, NULL); + g_object_set (sendrtcp_udpsink, "async", FALSE, NULL); + sendrtcp_udpsrc = gst_element_factory_make ("udpsrc", "sendrtcp_udpsrc"); + g_object_set (sendrtcp_udpsrc, "port", 5009, NULL); + + rtpbinreceive = gst_element_factory_make ("rtpbin", "rtpbinreceive"); + g_object_set (rtpbinreceive, "latency", 200, "do-retransmission", TRUE, NULL); + recvrtp_udpsrc = gst_element_factory_make ("udpsrc", "recvrtp_udpsrc"); + g_object_set (recvrtp_udpsrc, "port", 5006, NULL); + rtpcaps = + gst_caps_from_string + ("application/x-rtp,media=(string)audio,clock-rate=(int)8000,encoding-name=(string)SPEEX,encoding-params=(string)1,octet-align=(string)1"); + g_object_set (recvrtp_udpsrc, "caps", rtpcaps, NULL); + gst_caps_unref (rtpcaps); + recvrtcp_udpsrc = gst_element_factory_make ("udpsrc", "recvrtcp_udpsrc"); + g_object_set (recvrtcp_udpsrc, "port", 5007, NULL); + recvrtcp_udpsink = gst_element_factory_make ("udpsink", "recvrtcp_udpsink"); + g_object_set (recvrtcp_udpsink, "host", "127.0.0.1", NULL); + g_object_set (recvrtcp_udpsink, "port", 5009, NULL); + g_object_set (recvrtcp_udpsink, "sync", FALSE, NULL); + g_object_set (recvrtcp_udpsink, "async", FALSE, NULL); + rtprtxreceive = gst_element_factory_make ("rtprtxreceive", "rtprtxreceive"); + rtpdepayloader = gst_element_factory_make ("rtpspeexdepay", "rtpdepayloader"); + decoder = gst_element_factory_make ("speexdec", "decoder"); + converter = gst_element_factory_make ("identity", "converter"); + sink = gst_element_factory_make ("fakesink", "sink"); + g_object_set (sink, "sync", TRUE, NULL); + + gst_bin_add_many (GST_BIN (binsend), rtpbinsend, src, encoder, rtppayloader, + sendrtp_udpsink, sendrtcp_udpsink, sendrtcp_udpsrc, NULL); + + gst_bin_add_many (GST_BIN (binreceive), rtpbinreceive, + recvrtp_udpsrc, recvrtcp_udpsrc, recvrtcp_udpsink, + rtpdepayloader, decoder, converter, sink, NULL); + + g_signal_connect (rtpbinreceive, "pad-added", + G_CALLBACK (on_rtpbinreceive_pad_added), rtpdepayloader); + + pt_map = gst_structure_new ("application/x-rtp-pt-map", + "96", G_TYPE_UINT, 99, NULL); + g_object_set (rtppayloader, "pt", 96, NULL); + g_object_set (rtppayloader, "seqnum-offset", 1, NULL); + g_object_set (rtprtxsend, "payload-type-map", pt_map, NULL); + g_object_set (rtprtxreceive, "payload-type-map", pt_map, NULL); + gst_structure_free (pt_map); + + /* set rtp aux receive */ + g_signal_connect (rtpbinreceive, "request-aux-receiver", (GCallback) + request_aux_receive, rtprtxreceive); + /* set rtp aux send */ + g_signal_connect (rtpbinsend, "request-aux-sender", (GCallback) + request_aux_send, rtprtxsend); + + /* gst-launch-1.0 rtpbin name=rtpbin audiotestsrc ! amrnbenc ! rtpamrpay ! \ + * rtpbin.send_rtp_sink_1 rtpbin.send_rtp_src_1 ! udpsink host="127.0.0.1" \ + * port=5002 rtpbin.send_rtcp_src_1 ! udpsink host="127.0.0.1" port=5003 \ + * sync=false async=false udpsrc port=5007 ! rtpbin.recv_rtcp_sink_1 + */ + + res = gst_element_link (src, encoder); + fail_unless (res == TRUE, NULL); + res = gst_element_link (encoder, rtppayloader); + fail_unless (res == TRUE, NULL); + res = + gst_element_link_pads_full (rtppayloader, "src", rtpbinsend, + "send_rtp_sink_0", GST_PAD_LINK_CHECK_NOTHING); + fail_unless (res == TRUE, NULL); + res = + gst_element_link_pads_full (rtpbinsend, "send_rtp_src_0", sendrtp_udpsink, + "sink", GST_PAD_LINK_CHECK_NOTHING); + fail_unless (res == TRUE, NULL); + res = + gst_element_link_pads_full (rtpbinsend, "send_rtcp_src_0", + sendrtcp_udpsink, "sink", GST_PAD_LINK_CHECK_NOTHING); + fail_unless (res == TRUE, NULL); + res = + gst_element_link_pads_full (sendrtcp_udpsrc, "src", rtpbinsend, + "recv_rtcp_sink_0", GST_PAD_LINK_CHECK_NOTHING); + fail_unless (res == TRUE, NULL); + + srcpad = gst_element_get_static_pad (rtpbinsend, "send_rtp_src_0"); + gst_pad_add_probe (srcpad, + (GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_PUSH), + (GstPadProbeCallback) rtprtxsend_srcpad_probe, &send_rtxdata, NULL); + gst_object_unref (srcpad); + + /* gst-launch-1.0 rtpbin name=rtpbin udpsrc caps="application/x-rtp,media=(string)audio, \ + * clock-rate=(int)8000,encoding-name=(string)AMR,encoding-params=(string)1,o + * ctet-align=(string)1" port=5002 ! rtpbin.recv_rtp_sink_1 rtpbin. ! rtpamrdepay ! \ + * amrnbdec ! fakesink sync=True udpsrc port=5003 ! rtpbin.recv_rtcp_sink_1 \ + * rtpbin.send_rtcp_src_1 ! udpsink host="127.0.0.1" port=5007 sync=false async=false + */ + + res = + gst_element_link_pads_full (recvrtp_udpsrc, "src", rtpbinreceive, + "recv_rtp_sink_0", GST_PAD_LINK_CHECK_NOTHING); + fail_unless (res == TRUE, NULL); + res = + gst_element_link_pads_full (rtpdepayloader, "src", decoder, "sink", + GST_PAD_LINK_CHECK_NOTHING); + fail_unless (res == TRUE, NULL); + res = gst_element_link (decoder, converter); + fail_unless (res == TRUE, NULL); + res = + gst_element_link_pads_full (converter, "src", sink, "sink", + GST_PAD_LINK_CHECK_NOTHING); + fail_unless (res == TRUE, NULL); + res = + gst_element_link_pads_full (recvrtcp_udpsrc, "src", rtpbinreceive, + "recv_rtcp_sink_0", GST_PAD_LINK_CHECK_NOTHING); + fail_unless (res == TRUE, NULL); + res = + gst_element_link_pads_full (rtpbinreceive, "send_rtcp_src_0", + recvrtcp_udpsink, "sink", GST_PAD_LINK_CHECK_NOTHING); + fail_unless (res == TRUE, NULL); + + g_signal_connect (bussend, "message::error", (GCallback) message_received, + binsend); + g_signal_connect (bussend, "message::warning", (GCallback) message_received, + binsend); + g_signal_connect (bussend, "message::eos", (GCallback) message_received, + binsend); + + g_signal_connect (busreceive, "message::error", (GCallback) message_received, + binreceive); + g_signal_connect (busreceive, "message::warning", + (GCallback) message_received, binreceive); + g_signal_connect (busreceive, "message::eos", (GCallback) message_received, + binreceive); + + state_res = gst_element_set_state (binreceive, GST_STATE_PLAYING); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + state_res = gst_element_set_state (binsend, GST_STATE_PLAYING); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + g_timeout_add (5000, on_timeout, binsend); + g_timeout_add (5000, on_timeout, binreceive); + + GST_INFO ("enter mainloop"); + while (!send_pipeline_eos && !receive_pipeline_eos) + g_main_context_iteration (NULL, TRUE); + GST_INFO ("exit mainloop"); + + /* check that FB NACK is working */ + g_object_get (G_OBJECT (rtprtxsend), "num-rtx-requests", &nb_rtx_send_packets, + NULL); + g_object_get (G_OBJECT (rtprtxreceive), "num-rtx-requests", + &nb_rtx_recv_packets, NULL); + + state_res = gst_element_set_state (binsend, GST_STATE_NULL); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + state_res = gst_element_set_state (binreceive, GST_STATE_NULL); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + GST_INFO ("nb_rtx_send_packets %d", nb_rtx_send_packets); + GST_INFO ("nb_rtx_recv_packets %d", nb_rtx_recv_packets); + fail_if (nb_rtx_send_packets < 1); + fail_if (nb_rtx_recv_packets < 1); + + /* cleanup */ + gst_bus_remove_signal_watch (bussend); + gst_object_unref (bussend); + gst_object_unref (binsend); + + gst_bus_remove_signal_watch (busreceive); + gst_object_unref (busreceive); + gst_object_unref (binreceive); +} + +GST_END_TEST; + +static Suite * +rtpaux_suite (void) +{ + Suite *s = suite_create ("rtpaux"); + TCase *tc_chain = tcase_create ("general"); + + tcase_set_timeout (tc_chain, 10000); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_simple_rtpbin_aux); + + return s; +} + +GST_CHECK_MAIN (rtpaux); diff --git a/tests/check/elements/rtpbin.c b/tests/check/elements/rtpbin.c new file mode 100755 index 0000000..fc52b38 --- /dev/null +++ b/tests/check/elements/rtpbin.c @@ -0,0 +1,724 @@ +/* GStreamer + * + * unit test for gstrtpbin + * + * Copyright (C) <2009> Wim Taymans <wim.taymans@gmail.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. + */ + +#include <gst/check/gstcheck.h> + +GST_START_TEST (test_pads) +{ + GstElement *element; + GstPad *pad; + + element = gst_element_factory_make ("rtpsession", NULL); + + pad = gst_element_get_request_pad (element, "recv_rtcp_sink"); + gst_object_unref (pad); + gst_object_unref (element); +} + +GST_END_TEST; + +GST_START_TEST (test_cleanup_send) +{ + GstElement *rtpbin; + GstPad *rtp_sink, *rtp_src, *rtcp_src; + GObject *session; + gint count = 2; + + rtpbin = gst_element_factory_make ("rtpbin", "rtpbin"); + + while (count--) { + /* request session 0 */ + rtp_sink = gst_element_get_request_pad (rtpbin, "send_rtp_sink_0"); + fail_unless (rtp_sink != NULL); + ASSERT_OBJECT_REFCOUNT (rtp_sink, "rtp_sink", 2); + + /* this static pad should be created automatically now */ + rtp_src = gst_element_get_static_pad (rtpbin, "send_rtp_src_0"); + fail_unless (rtp_src != NULL); + ASSERT_OBJECT_REFCOUNT (rtp_src, "rtp_src", 2); + + /* we should be able to get an internal session 0 now */ + g_signal_emit_by_name (rtpbin, "get-internal-session", 0, &session); + fail_unless (session != NULL); + g_object_unref (session); + + /* get the send RTCP pad too */ + rtcp_src = gst_element_get_request_pad (rtpbin, "send_rtcp_src_0"); + fail_unless (rtcp_src != NULL); + ASSERT_OBJECT_REFCOUNT (rtcp_src, "rtcp_src", 2); + + gst_element_release_request_pad (rtpbin, rtp_sink); + /* we should only have our refs to the pads now */ + ASSERT_OBJECT_REFCOUNT (rtp_sink, "rtp_sink", 1); + ASSERT_OBJECT_REFCOUNT (rtp_src, "rtp_src", 1); + ASSERT_OBJECT_REFCOUNT (rtcp_src, "rtp_src", 2); + + /* the other pad should be gone now */ + fail_unless (gst_element_get_static_pad (rtpbin, "send_rtp_src_0") == NULL); + + /* internal session should still be there */ + g_signal_emit_by_name (rtpbin, "get-internal-session", 0, &session); + fail_unless (session != NULL); + g_object_unref (session); + + /* release the RTCP pad */ + gst_element_release_request_pad (rtpbin, rtcp_src); + /* we should only have our refs to the pads now */ + ASSERT_OBJECT_REFCOUNT (rtp_sink, "rtp_sink", 1); + ASSERT_OBJECT_REFCOUNT (rtp_src, "rtp_src", 1); + ASSERT_OBJECT_REFCOUNT (rtcp_src, "rtp_src", 1); + + /* the session should be gone now */ + g_signal_emit_by_name (rtpbin, "get-internal-session", 0, &session); + fail_unless (session == NULL); + + /* unref the request pad and the static pad */ + gst_object_unref (rtp_sink); + gst_object_unref (rtp_src); + gst_object_unref (rtcp_src); + } + + gst_object_unref (rtpbin); +} + +GST_END_TEST; + +typedef struct +{ + guint16 seqnum; + gboolean pad_added; + GstPad *pad; + GMutex lock; + GCond cond; + GstPad *sinkpad; + GList *pads; +} CleanupData; + +static void +init_data (CleanupData * data) +{ + data->seqnum = 10; + data->pad_added = FALSE; + g_mutex_init (&data->lock); + g_cond_init (&data->cond); + data->pads = NULL; +} + +static void +clean_data (CleanupData * data) +{ + g_list_foreach (data->pads, (GFunc) gst_object_unref, NULL); + g_list_free (data->pads); + g_mutex_clear (&data->lock); + g_cond_clear (&data->cond); +} + +static guint8 rtp_packet[] = { 0x80, 0x60, 0x94, 0xbc, 0x8f, 0x37, 0x4e, 0xb8, + 0x44, 0xa8, 0xf3, 0x7c, 0x06, 0x6a, 0x0c, 0xce, + 0x13, 0x25, 0x19, 0x69, 0x1f, 0x93, 0x25, 0x9d, + 0x2b, 0x82, 0x31, 0x3b, 0x36, 0xc1, 0x3c, 0x13 +}; + +static GstFlowReturn +chain_rtp_packet (GstPad * pad, CleanupData * data) +{ + GstFlowReturn res; + static GstCaps *caps = NULL; + GstSegment segment; + GstBuffer *buffer; + GstMapInfo map; + + if (caps == NULL) { + caps = gst_caps_from_string ("application/x-rtp," + "media=(string)audio, clock-rate=(int)44100, " + "encoding-name=(string)L16, encoding-params=(string)1, channels=(int)1"); + data->seqnum = 0; + } + + gst_pad_send_event (pad, gst_event_new_stream_start (GST_OBJECT_NAME (pad))); + gst_pad_send_event (pad, gst_event_new_caps (caps)); + gst_segment_init (&segment, GST_FORMAT_TIME); + gst_pad_send_event (pad, gst_event_new_segment (&segment)); + + buffer = gst_buffer_new_and_alloc (sizeof (rtp_packet)); + gst_buffer_map (buffer, &map, GST_MAP_WRITE); + memcpy (map.data, rtp_packet, sizeof (rtp_packet)); + + map.data[2] = (data->seqnum >> 8) & 0xff; + map.data[3] = data->seqnum & 0xff; + + data->seqnum++; + gst_buffer_unmap (buffer, &map); + + res = gst_pad_chain (pad, buffer); + + return res; +} + +static GstFlowReturn +dummy_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) +{ + gst_buffer_unref (buffer); + + return GST_FLOW_OK; +} + +static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-rtp")); + + +static GstPad * +make_sinkpad (CleanupData * data) +{ + GstPad *pad; + + pad = gst_pad_new_from_static_template (&sink_factory, "sink"); + + gst_pad_set_chain_function (pad, dummy_chain); + gst_pad_set_active (pad, TRUE); + + data->pads = g_list_prepend (data->pads, pad); + + return pad; +} + +static void +pad_added_cb (GstElement * rtpbin, GstPad * pad, CleanupData * data) +{ + GstPad *sinkpad; + + GST_DEBUG ("pad added %s:%s\n", GST_DEBUG_PAD_NAME (pad)); + + if (GST_PAD_IS_SINK (pad)) + return; + + fail_unless (data->pad_added == FALSE); + + sinkpad = make_sinkpad (data); + fail_unless (gst_pad_link (pad, sinkpad) == GST_PAD_LINK_OK); + + g_mutex_lock (&data->lock); + data->pad_added = TRUE; + data->pad = pad; + g_cond_signal (&data->cond); + g_mutex_unlock (&data->lock); +} + +static void +pad_removed_cb (GstElement * rtpbin, GstPad * pad, CleanupData * data) +{ + GST_DEBUG ("pad removed %s:%s\n", GST_DEBUG_PAD_NAME (pad)); + + if (data->pad != pad) + return; + + fail_unless (data->pad_added == TRUE); + + g_mutex_lock (&data->lock); + data->pad_added = FALSE; + g_cond_signal (&data->cond); + g_mutex_unlock (&data->lock); +} + +GST_START_TEST (test_cleanup_recv) +{ + GstElement *rtpbin; + GstPad *rtp_sink; + CleanupData data; + GstStateChangeReturn ret; + GstFlowReturn res; + gint count = 2; + + init_data (&data); + + rtpbin = gst_element_factory_make ("rtpbin", "rtpbin"); + + g_signal_connect (rtpbin, "pad-added", (GCallback) pad_added_cb, &data); + g_signal_connect (rtpbin, "pad-removed", (GCallback) pad_removed_cb, &data); + + ret = gst_element_set_state (rtpbin, GST_STATE_PLAYING); + fail_unless (ret == GST_STATE_CHANGE_SUCCESS); + + while (count--) { + /* request session 0 */ + rtp_sink = gst_element_get_request_pad (rtpbin, "recv_rtp_sink_0"); + fail_unless (rtp_sink != NULL); + ASSERT_OBJECT_REFCOUNT (rtp_sink, "rtp_sink", 2); + + /* no sourcepads are created yet */ + fail_unless (rtpbin->numsinkpads == 1); + fail_unless (rtpbin->numsrcpads == 0); + + res = chain_rtp_packet (rtp_sink, &data); + GST_DEBUG ("res %d, %s\n", res, gst_flow_get_name (res)); + fail_unless (res == GST_FLOW_OK); + + res = chain_rtp_packet (rtp_sink, &data); + GST_DEBUG ("res %d, %s\n", res, gst_flow_get_name (res)); + fail_unless (res == GST_FLOW_OK); + + /* we wait for the new pad to appear now */ + g_mutex_lock (&data.lock); + while (!data.pad_added) + g_cond_wait (&data.cond, &data.lock); + g_mutex_unlock (&data.lock); + + /* sourcepad created now */ + fail_unless (rtpbin->numsinkpads == 1); + fail_unless (rtpbin->numsrcpads == 1); + + /* remove the session */ + gst_element_release_request_pad (rtpbin, rtp_sink); + gst_object_unref (rtp_sink); + + /* pad should be gone now */ + g_mutex_lock (&data.lock); + while (data.pad_added) + g_cond_wait (&data.cond, &data.lock); + g_mutex_unlock (&data.lock); + + /* nothing left anymore now */ + fail_unless (rtpbin->numsinkpads == 0); + fail_unless (rtpbin->numsrcpads == 0); + } + + ret = gst_element_set_state (rtpbin, GST_STATE_NULL); + fail_unless (ret == GST_STATE_CHANGE_SUCCESS); + + gst_object_unref (rtpbin); + + clean_data (&data); +} + +GST_END_TEST; + +GST_START_TEST (test_cleanup_recv2) +{ + GstElement *rtpbin; + GstPad *rtp_sink; + CleanupData data; + GstStateChangeReturn ret; + GstFlowReturn res; + gint count = 2; + + init_data (&data); + + rtpbin = gst_element_factory_make ("rtpbin", "rtpbin"); + + g_signal_connect (rtpbin, "pad-added", (GCallback) pad_added_cb, &data); + g_signal_connect (rtpbin, "pad-removed", (GCallback) pad_removed_cb, &data); + + ret = gst_element_set_state (rtpbin, GST_STATE_PLAYING); + fail_unless (ret == GST_STATE_CHANGE_SUCCESS); + + /* request session 0 */ + rtp_sink = gst_element_get_request_pad (rtpbin, "recv_rtp_sink_0"); + fail_unless (rtp_sink != NULL); + ASSERT_OBJECT_REFCOUNT (rtp_sink, "rtp_sink", 2); + + while (count--) { + /* no sourcepads are created yet */ + fail_unless (rtpbin->numsinkpads == 1); + fail_unless (rtpbin->numsrcpads == 0); + + res = chain_rtp_packet (rtp_sink, &data); + GST_DEBUG ("res %d, %s\n", res, gst_flow_get_name (res)); + fail_unless (res == GST_FLOW_OK); + + res = chain_rtp_packet (rtp_sink, &data); + GST_DEBUG ("res %d, %s\n", res, gst_flow_get_name (res)); + fail_unless (res == GST_FLOW_OK); + + /* we wait for the new pad to appear now */ + g_mutex_lock (&data.lock); + while (!data.pad_added) + g_cond_wait (&data.cond, &data.lock); + g_mutex_unlock (&data.lock); + + /* sourcepad created now */ + fail_unless (rtpbin->numsinkpads == 1); + fail_unless (rtpbin->numsrcpads == 1); + + /* change state */ + ret = gst_element_set_state (rtpbin, GST_STATE_NULL); + fail_unless (ret == GST_STATE_CHANGE_SUCCESS); + + /* pad should be gone now */ + g_mutex_lock (&data.lock); + while (data.pad_added) + g_cond_wait (&data.cond, &data.lock); + g_mutex_unlock (&data.lock); + + /* back to playing for the next round */ + ret = gst_element_set_state (rtpbin, GST_STATE_PLAYING); + fail_unless (ret == GST_STATE_CHANGE_SUCCESS); + } + + /* remove the session */ + gst_element_release_request_pad (rtpbin, rtp_sink); + gst_object_unref (rtp_sink); + + /* nothing left anymore now */ + fail_unless (rtpbin->numsinkpads == 0); + fail_unless (rtpbin->numsrcpads == 0); + + ret = gst_element_set_state (rtpbin, GST_STATE_NULL); + fail_unless (ret == GST_STATE_CHANGE_SUCCESS); + + gst_object_unref (rtpbin); + + clean_data (&data); +} + +GST_END_TEST; + +GST_START_TEST (test_request_pad_by_template_name) +{ + GstElement *rtpbin; + GstPad *rtp_sink1, *rtp_sink2, *rtp_sink3; + + rtpbin = gst_element_factory_make ("rtpbin", "rtpbin"); + rtp_sink1 = gst_element_get_request_pad (rtpbin, "recv_rtp_sink_%u"); + fail_unless (rtp_sink1 != NULL); + fail_unless_equals_string (GST_PAD_NAME (rtp_sink1), "recv_rtp_sink_0"); + ASSERT_OBJECT_REFCOUNT (rtp_sink1, "rtp_sink1", 2); + + rtp_sink2 = gst_element_get_request_pad (rtpbin, "recv_rtp_sink_%u"); + fail_unless (rtp_sink2 != NULL); + fail_unless_equals_string (GST_PAD_NAME (rtp_sink2), "recv_rtp_sink_1"); + ASSERT_OBJECT_REFCOUNT (rtp_sink2, "rtp_sink2", 2); + + rtp_sink3 = gst_element_get_request_pad (rtpbin, "recv_rtp_sink_%u"); + fail_unless (rtp_sink3 != NULL); + fail_unless_equals_string (GST_PAD_NAME (rtp_sink3), "recv_rtp_sink_2"); + ASSERT_OBJECT_REFCOUNT (rtp_sink3, "rtp_sink3", 2); + + + gst_element_release_request_pad (rtpbin, rtp_sink2); + gst_element_release_request_pad (rtpbin, rtp_sink1); + gst_element_release_request_pad (rtpbin, rtp_sink3); + ASSERT_OBJECT_REFCOUNT (rtp_sink3, "rtp_sink3", 1); + ASSERT_OBJECT_REFCOUNT (rtp_sink2, "rtp_sink2", 1); + ASSERT_OBJECT_REFCOUNT (rtp_sink1, "rtp_sink", 1); + gst_object_unref (rtp_sink1); + gst_object_unref (rtp_sink2); + gst_object_unref (rtp_sink3); + + gst_object_unref (rtpbin); +} + +GST_END_TEST; + +static GstElement * +encoder_cb (GstElement * rtpbin, guint sessid, GstElement * bin) +{ + GstPad *srcpad, *sinkpad; + + fail_unless (sessid == 2); + + GST_DEBUG ("making encoder"); + sinkpad = gst_ghost_pad_new_no_target ("rtp_sink_2", GST_PAD_SINK); + srcpad = gst_ghost_pad_new_no_target ("rtp_src_2", GST_PAD_SRC); + + gst_element_add_pad (bin, sinkpad); + gst_element_add_pad (bin, srcpad); + + return gst_object_ref (bin); +} + +static GstElement * +encoder_cb2 (GstElement * rtpbin, guint sessid, GstElement * bin) +{ + GstPad *srcpad, *sinkpad; + + fail_unless (sessid == 3); + + GST_DEBUG ("making encoder"); + sinkpad = gst_ghost_pad_new_no_target ("rtp_sink_3", GST_PAD_SINK); + srcpad = gst_ghost_pad_new_no_target ("rtp_src_3", GST_PAD_SRC); + + gst_element_add_pad (bin, sinkpad); + gst_element_add_pad (bin, srcpad); + + return gst_object_ref (bin); +} + +GST_START_TEST (test_encoder) +{ + GstElement *rtpbin, *bin; + GstPad *rtp_sink1, *rtp_sink2; + gulong id; + + bin = gst_bin_new ("rtpenc"); + + rtpbin = gst_element_factory_make ("rtpbin", "rtpbin"); + + id = g_signal_connect (rtpbin, "request-rtp-encoder", (GCallback) encoder_cb, + bin); + + rtp_sink1 = gst_element_get_request_pad (rtpbin, "send_rtp_sink_2"); + fail_unless (rtp_sink1 != NULL); + fail_unless_equals_string (GST_PAD_NAME (rtp_sink1), "send_rtp_sink_2"); + ASSERT_OBJECT_REFCOUNT (rtp_sink1, "rtp_sink1", 2); + + g_signal_handler_disconnect (rtpbin, id); + + id = g_signal_connect (rtpbin, "request-rtp-encoder", (GCallback) encoder_cb2, + bin); + + rtp_sink2 = gst_element_get_request_pad (rtpbin, "send_rtp_sink_3"); + fail_unless (rtp_sink2 != NULL); + + /* remove the session */ + gst_element_release_request_pad (rtpbin, rtp_sink1); + gst_object_unref (rtp_sink1); + + gst_element_release_request_pad (rtpbin, rtp_sink2); + gst_object_unref (rtp_sink2); + + /* nothing left anymore now */ + fail_unless (rtpbin->numsinkpads == 0); + fail_unless (rtpbin->numsrcpads == 0); + + gst_object_unref (rtpbin); + gst_object_unref (bin); +} + +GST_END_TEST; + +static GstElement * +decoder_cb (GstElement * rtpbin, guint sessid, gpointer user_data) +{ + GstElement *bin; + GstPad *srcpad, *sinkpad; + + bin = gst_bin_new (NULL); + + GST_DEBUG ("making decoder"); + sinkpad = gst_ghost_pad_new_no_target ("rtp_sink", GST_PAD_SINK); + srcpad = gst_ghost_pad_new_no_target ("rtp_src", GST_PAD_SRC); + + gst_element_add_pad (bin, sinkpad); + gst_element_add_pad (bin, srcpad); + + return bin; +} + +GST_START_TEST (test_decoder) +{ + GstElement *rtpbin; + GstPad *rtp_sink1, *rtp_sink2; + gulong id; + + + rtpbin = gst_element_factory_make ("rtpbin", "rtpbin"); + + id = g_signal_connect (rtpbin, "request-rtp-decoder", (GCallback) decoder_cb, + NULL); + + rtp_sink1 = gst_element_get_request_pad (rtpbin, "recv_rtp_sink_2"); + fail_unless (rtp_sink1 != NULL); + fail_unless_equals_string (GST_PAD_NAME (rtp_sink1), "recv_rtp_sink_2"); + ASSERT_OBJECT_REFCOUNT (rtp_sink1, "rtp_sink1", 2); + + rtp_sink2 = gst_element_get_request_pad (rtpbin, "recv_rtp_sink_3"); + fail_unless (rtp_sink2 != NULL); + + g_signal_handler_disconnect (rtpbin, id); + + /* remove the session */ + gst_element_release_request_pad (rtpbin, rtp_sink1); + gst_object_unref (rtp_sink1); + + gst_element_release_request_pad (rtpbin, rtp_sink2); + gst_object_unref (rtp_sink2); + + /* nothing left anymore now */ + fail_unless (rtpbin->numsinkpads == 0); + fail_unless (rtpbin->numsrcpads == 0); + + gst_object_unref (rtpbin); +} + +GST_END_TEST; + +static GstElement * +aux_sender_cb (GstElement * rtpbin, guint sessid, gpointer user_data) +{ + GstElement *bin; + GstPad *srcpad, *sinkpad; + + bin = gst_bin_new (NULL); + + GST_DEBUG ("making AUX sender"); + sinkpad = gst_ghost_pad_new_no_target ("sink_2", GST_PAD_SINK); + gst_element_add_pad (bin, sinkpad); + + srcpad = gst_ghost_pad_new_no_target ("src_2", GST_PAD_SRC); + gst_element_add_pad (bin, srcpad); + srcpad = gst_ghost_pad_new_no_target ("src_1", GST_PAD_SRC); + gst_element_add_pad (bin, srcpad); + srcpad = gst_ghost_pad_new_no_target ("src_3", GST_PAD_SRC); + gst_element_add_pad (bin, srcpad); + + return bin; +} + +GST_START_TEST (test_aux_sender) +{ + GstElement *rtpbin; + GstPad *rtp_sink1, *rtp_src, *rtcp_src; + gulong id; + + rtpbin = gst_element_factory_make ("rtpbin", "rtpbin"); + + id = g_signal_connect (rtpbin, "request-aux-sender", + (GCallback) aux_sender_cb, NULL); + + rtp_sink1 = gst_element_get_request_pad (rtpbin, "send_rtp_sink_2"); + fail_unless (rtp_sink1 != NULL); + fail_unless_equals_string (GST_PAD_NAME (rtp_sink1), "send_rtp_sink_2"); + ASSERT_OBJECT_REFCOUNT (rtp_sink1, "rtp_sink1", 2); + + g_signal_handler_disconnect (rtpbin, id); + + rtp_src = gst_element_get_static_pad (rtpbin, "send_rtp_src_2"); + fail_unless (rtp_src != NULL); + gst_object_unref (rtp_src); + + rtp_src = gst_element_get_static_pad (rtpbin, "send_rtp_src_1"); + fail_unless (rtp_src != NULL); + gst_object_unref (rtp_src); + + rtcp_src = gst_element_get_request_pad (rtpbin, "send_rtcp_src_1"); + fail_unless (rtcp_src != NULL); + gst_element_release_request_pad (rtpbin, rtcp_src); + gst_object_unref (rtcp_src); + + rtp_src = gst_element_get_static_pad (rtpbin, "send_rtp_src_3"); + fail_unless (rtp_src != NULL); + gst_object_unref (rtp_src); + + /* remove the session */ + gst_element_release_request_pad (rtpbin, rtp_sink1); + gst_object_unref (rtp_sink1); + + gst_object_unref (rtpbin); +} + +GST_END_TEST; + +static GstElement * +aux_receiver_cb (GstElement * rtpbin, guint sessid, gpointer user_data) +{ + GstElement *bin; + GstPad *srcpad, *sinkpad; + + bin = gst_bin_new (NULL); + + GST_DEBUG ("making AUX receiver"); + srcpad = gst_ghost_pad_new_no_target ("src_2", GST_PAD_SRC); + gst_element_add_pad (bin, srcpad); + + sinkpad = gst_ghost_pad_new_no_target ("sink_2", GST_PAD_SINK); + gst_element_add_pad (bin, sinkpad); + sinkpad = gst_ghost_pad_new_no_target ("sink_1", GST_PAD_SINK); + gst_element_add_pad (bin, sinkpad); + sinkpad = gst_ghost_pad_new_no_target ("sink_3", GST_PAD_SINK); + gst_element_add_pad (bin, sinkpad); + + return bin; +} + +GST_START_TEST (test_aux_receiver) +{ + GstElement *rtpbin; + GstPad *rtp_sink1, *rtp_sink2, *rtcp_sink; + gulong id; + + rtpbin = gst_element_factory_make ("rtpbin", "rtpbin"); + + id = g_signal_connect (rtpbin, "request-aux-receiver", + (GCallback) aux_receiver_cb, NULL); + + rtp_sink1 = gst_element_get_request_pad (rtpbin, "recv_rtp_sink_2"); + fail_unless (rtp_sink1 != NULL); + + rtp_sink2 = gst_element_get_request_pad (rtpbin, "recv_rtp_sink_1"); + fail_unless (rtp_sink2 != NULL); + + g_signal_handler_disconnect (rtpbin, id); + + rtcp_sink = gst_element_get_request_pad (rtpbin, "recv_rtcp_sink_1"); + fail_unless (rtcp_sink != NULL); + gst_element_release_request_pad (rtpbin, rtcp_sink); + gst_object_unref (rtcp_sink); + + /* remove the session */ + gst_element_release_request_pad (rtpbin, rtp_sink1); + gst_object_unref (rtp_sink1); + gst_element_release_request_pad (rtpbin, rtp_sink2); + gst_object_unref (rtp_sink2); + + gst_object_unref (rtpbin); +} + +GST_END_TEST; + +static Suite * +gstrtpbin_suite (void) +{ + Suite *s = suite_create ("rtpbin"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_pads); + tcase_add_test (tc_chain, test_cleanup_send); + tcase_add_test (tc_chain, test_cleanup_recv); + tcase_add_test (tc_chain, test_cleanup_recv2); + tcase_add_test (tc_chain, test_request_pad_by_template_name); + tcase_add_test (tc_chain, test_encoder); + tcase_add_test (tc_chain, test_decoder); + tcase_add_test (tc_chain, test_aux_sender); + tcase_add_test (tc_chain, test_aux_receiver); + + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = gstrtpbin_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/rtpbin_buffer_list.c b/tests/check/elements/rtpbin_buffer_list.c new file mode 100644 index 0000000..b6a7793 --- /dev/null +++ b/tests/check/elements/rtpbin_buffer_list.c @@ -0,0 +1,335 @@ +/* GStreamer + * + * Unit test for gstrtpbin sending rtp packets using GstBufferList. + * Copyright (C) 2009 Branko Subasic <branko dot subasic at axis dot 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. + */ + +#include <gst/check/gstcheck.h> + +#include <gst/rtp/gstrtpbuffer.h> + + +#if 0 + +/* This test makes sure that RTP packets sent as buffer lists are sent through + * the rtpbin as they are supposed to, and not corrupted in any way. + */ + + +#define TEST_CAPS \ + "application/x-rtp, " \ + "media=(string)video, " \ + "clock-rate=(int)90000, " \ + "encoding-name=(string)H264, " \ + "profile-level-id=(string)4d4015, " \ + "payload=(int)96, " \ + "ssrc=(guint)2633237432, " \ + "clock-base=(guint)1868267015, " \ + "seqnum-base=(guint)54229" + + +/* RTP headers and the first 2 bytes of the payload (FU indicator and FU header) + */ +static const guint8 rtp_header[2][14] = { + {0x80, 0x60, 0xbb, 0xb7, 0x5c, 0xe9, 0x09, + 0x0d, 0xf5, 0x9c, 0x43, 0x55, 0x1c, 0x86}, + {0x80, 0x60, 0xbb, 0xb8, 0x5c, 0xe9, 0x09, + 0x0d, 0xf5, 0x9c, 0x43, 0x55, 0x1c, 0x46} +}; + +static const guint rtp_header_len[] = { + sizeof rtp_header[0], + sizeof rtp_header[1] +}; + +static GstBuffer *header_buffer[2] = { NULL, NULL }; + + +/* Some payload. + */ +static const char *payload = + "0123456789ABSDEF0123456789ABSDEF0123456789ABSDEF0123456789ABSDEF0123456789ABSDEF" + "0123456789ABSDEF0123456789ABSDEF0123456789ABSDEF0123456789ABSDEF0123456789ABSDEF" + "0123456789ABSDEF0123456789ABSDEF0123456789ABSDEF0123456789ABSDEF0123456789ABSDEF" + "0123456789ABSDEF0123456789ABSDEF0123456789ABSDEF0123456789ABSDEF0123456789ABSDEF" + "0123456789ABSDEF0123456789ABSDEF0123456789ABSDEF0123456789ABSDEF0123456789ABSDEF" + "0123456789ABSDEF0123456789ABSDEF0123456789ABSDEF0123456789ABSDEF0123456789ABSDEF" + "0123456789ABSDEF0123456"; + +static const guint payload_offset[] = { + 0, 498 +}; + +static const guint payload_len[] = { + 498, 5 +}; + + +static GstBuffer *original_buffer = NULL; + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-rtp")); + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-rtp")); + + +static GstBuffer * +_create_original_buffer (void) +{ + GstCaps *caps; + + if (original_buffer != NULL) + return original_buffer; + + original_buffer = gst_buffer_new (); + fail_unless (original_buffer != NULL); + + gst_buffer_set_data (original_buffer, (guint8 *) payload, strlen (payload)); + GST_BUFFER_TIMESTAMP (original_buffer) = + gst_clock_get_internal_time (gst_system_clock_obtain ()); + + caps = gst_caps_from_string (TEST_CAPS); + fail_unless (caps != NULL); + gst_buffer_set_caps (original_buffer, caps); + gst_caps_unref (caps); + + return original_buffer; +} + +static GstBufferList * +_create_buffer_list (void) +{ + GstBufferList *list; + GstBufferListIterator *it; + GstBuffer *orig_buffer; + GstBuffer *buffer; + + orig_buffer = _create_original_buffer (); + fail_if (orig_buffer == NULL); + + list = gst_buffer_list_new (); + fail_if (list == NULL); + + it = gst_buffer_list_iterate (list); + fail_if (it == NULL); + + /*** First group, i.e. first packet. **/ + gst_buffer_list_iterator_add_group (it); + + /* Create buffer with RTP header and add it to the 1st group */ + buffer = gst_buffer_new (); + GST_BUFFER_MALLOCDATA (buffer) = g_memdup (&rtp_header[0], rtp_header_len[0]); + GST_BUFFER_DATA (buffer) = GST_BUFFER_MALLOCDATA (buffer); + GST_BUFFER_SIZE (buffer) = rtp_header_len[0]; + gst_buffer_copy_metadata (buffer, orig_buffer, GST_BUFFER_COPY_ALL); + header_buffer[0] = buffer; + gst_buffer_list_iterator_add (it, buffer); + + /* Create the payload buffer and add it to the 1st group + */ + buffer = + gst_buffer_create_sub (orig_buffer, payload_offset[0], payload_len[0]); + fail_if (buffer == NULL); + gst_buffer_list_iterator_add (it, buffer); + + + /*** Second group, i.e. second packet. ***/ + + /* Create a new group to hold the rtp header and the payload */ + gst_buffer_list_iterator_add_group (it); + + /* Create buffer with RTP header and add it to the 2nd group */ + buffer = gst_buffer_new (); + GST_BUFFER_MALLOCDATA (buffer) = g_memdup (&rtp_header[1], rtp_header_len[1]); + GST_BUFFER_DATA (buffer) = GST_BUFFER_MALLOCDATA (buffer); + GST_BUFFER_SIZE (buffer) = rtp_header_len[1]; + gst_buffer_copy_metadata (buffer, orig_buffer, GST_BUFFER_COPY_ALL); + header_buffer[1] = buffer; + + /* Add the rtp header to the buffer list */ + gst_buffer_list_iterator_add (it, buffer); + + /* Create the payload buffer and add it to the 2d group + */ + buffer = + gst_buffer_create_sub (orig_buffer, payload_offset[1], payload_len[1]); + fail_if (buffer == NULL); + gst_buffer_list_iterator_add (it, buffer); + + gst_buffer_list_iterator_free (it); + + return list; +} + + +static void +_check_header (GstBuffer * buffer, guint index) +{ + guint8 *data; + + fail_if (buffer == NULL); + fail_unless (index < 2); + + fail_unless (GST_BUFFER_SIZE (buffer) == rtp_header_len[index]); + + /* Can't do a memcmp() on the whole header, cause the SSRC (bytes 8-11) will + * most likely be changed in gstrtpbin. + */ + fail_unless ((data = GST_BUFFER_DATA (buffer)) != NULL); + fail_unless_equals_uint64 (*(guint64 *) data, *(guint64 *) rtp_header[index]); + fail_unless (*(guint16 *) (data + 12) == + *(guint16 *) (rtp_header[index] + 12)); +} + + +static void +_check_payload (GstBuffer * buffer, guint index) +{ + fail_if (buffer == NULL); + fail_unless (index < 2); + + fail_unless (GST_BUFFER_SIZE (buffer) == payload_len[index]); + fail_if (GST_BUFFER_DATA (buffer) != + (gpointer) (payload + payload_offset[index])); + fail_if (memcmp (GST_BUFFER_DATA (buffer), payload + payload_offset[index], + payload_len[index])); +} + + +static void +_check_group (GstBufferListIterator * it, guint index, GstCaps * caps) +{ + GstBuffer *buffer; + + fail_unless (it != NULL); + fail_unless (gst_buffer_list_iterator_n_buffers (it) == 2); + fail_unless (caps != NULL); + + fail_unless ((buffer = gst_buffer_list_iterator_next (it)) != NULL); + + fail_unless (GST_BUFFER_TIMESTAMP (buffer) == + GST_BUFFER_TIMESTAMP (original_buffer)); + + fail_unless (gst_caps_is_equal (GST_BUFFER_CAPS (original_buffer), + GST_BUFFER_CAPS (buffer))); + + _check_header (buffer, index); + + fail_unless ((buffer = gst_buffer_list_iterator_next (it)) != NULL); + _check_payload (buffer, index); +} + + +static GstFlowReturn +_sink_chain_list (GstPad * pad, GstBufferList * list) +{ + GstCaps *caps; + GstBufferListIterator *it; + + caps = gst_caps_from_string (TEST_CAPS); + fail_unless (caps != NULL); + + fail_unless (GST_IS_BUFFER_LIST (list)); + fail_unless (gst_buffer_list_n_groups (list) == 2); + + it = gst_buffer_list_iterate (list); + fail_if (it == NULL); + + fail_unless (gst_buffer_list_iterator_next_group (it)); + _check_group (it, 0, caps); + + fail_unless (gst_buffer_list_iterator_next_group (it)); + _check_group (it, 1, caps); + + gst_caps_unref (caps); + gst_buffer_list_iterator_free (it); + + gst_buffer_list_unref (list); + + return GST_FLOW_OK; +} + + +static void +_set_chain_functions (GstPad * pad) +{ + gst_pad_set_chain_list_function (pad, _sink_chain_list); +} + + +GST_START_TEST (test_bufferlist) +{ + GstElement *rtpbin; + GstPad *sinkpad; + GstPad *srcpad; + GstBufferList *list; + + list = _create_buffer_list (); + fail_unless (list != NULL); + + rtpbin = gst_check_setup_element ("gstrtpbin"); + + srcpad = + gst_check_setup_src_pad_by_name (rtpbin, &srctemplate, "send_rtp_sink_0"); + fail_if (srcpad == NULL); + sinkpad = + gst_check_setup_sink_pad_by_name (rtpbin, &sinktemplate, + "send_rtp_src_0"); + fail_if (sinkpad == NULL); + + _set_chain_functions (sinkpad); + + gst_pad_set_active (sinkpad, TRUE); + gst_element_set_state (rtpbin, GST_STATE_PLAYING); + fail_unless (gst_pad_push_list (srcpad, list) == GST_FLOW_OK); + gst_pad_set_active (sinkpad, FALSE); + + gst_check_teardown_pad_by_name (rtpbin, "send_rtp_src_0"); + gst_check_teardown_pad_by_name (rtpbin, "send_rtp_sink_0"); + gst_check_teardown_element (rtpbin); +} + +GST_END_TEST; + +#endif + + +static Suite * +bufferlist_suite (void) +{ + Suite *s = suite_create ("BufferList"); + + TCase *tc_chain = tcase_create ("general"); + + /* time out after 30s. */ + tcase_set_timeout (tc_chain, 10); + + suite_add_tcase (s, tc_chain); +#if 0 + tcase_add_test (tc_chain, test_bufferlist); +#endif + + return s; +} + +GST_CHECK_MAIN (bufferlist); diff --git a/tests/check/elements/rtpcollision.c b/tests/check/elements/rtpcollision.c new file mode 100755 index 0000000..e9528f9 --- /dev/null +++ b/tests/check/elements/rtpcollision.c @@ -0,0 +1,462 @@ +/* GStreamer + * + * Copyright (C) 2013 Collabora Ltd. + * @author Julien Isorce <julien.isorce@collabora.co.uk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <gst/check/gstcheck.h> +#include <gst/net/gstnetaddressmeta.h> +#include <gst/rtp/gstrtpbuffer.h> +#include <gst/rtp/gstrtcpbuffer.h> + +static GMainLoop *main_loop; +static GstPad *srcpad; + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-rtcp") + ); + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-rtcp") + ); + +static void +message_received (GstBus * bus, GstMessage * message, GstPipeline * bin) +{ + GST_INFO ("bus message from \"%" GST_PTR_FORMAT "\": %" GST_PTR_FORMAT, + GST_MESSAGE_SRC (message), message); + + switch (message->type) { + case GST_MESSAGE_EOS: + g_main_loop_quit (main_loop); + break; + case GST_MESSAGE_WARNING:{ + GError *gerror; + gchar *debug; + + gst_message_parse_warning (message, &gerror, &debug); + gst_object_default_error (GST_MESSAGE_SRC (message), gerror, debug); + g_error_free (gerror); + g_free (debug); + break; + } + case GST_MESSAGE_ERROR:{ + GError *gerror; + gchar *debug; + + gst_message_parse_error (message, &gerror, &debug); + gst_object_default_error (GST_MESSAGE_SRC (message), gerror, debug); + g_error_free (gerror); + g_free (debug); + g_main_loop_quit (main_loop); + break; + } + default: + break; + } +} + +static GstBuffer * +create_rtcp_app (guint32 ssrc, guint count) +{ + GInetAddress *inet_addr_0; + guint16 port = 5678 + count; + GSocketAddress *socket_addr_0; + GstBuffer *rtcp_buffer; + GstRTCPPacket *rtcp_packet = NULL; + GstRTCPBuffer rtcp = GST_RTCP_BUFFER_INIT; + + inet_addr_0 = g_inet_address_new_from_string ("192.168.1.1"); + socket_addr_0 = g_inet_socket_address_new (inet_addr_0, port); + g_object_unref (inet_addr_0); + + rtcp_buffer = gst_rtcp_buffer_new (1400); + gst_buffer_add_net_address_meta (rtcp_buffer, socket_addr_0); + g_object_unref (socket_addr_0); + + /* need to begin with rr */ + gst_rtcp_buffer_map (rtcp_buffer, GST_MAP_READWRITE, &rtcp); + rtcp_packet = g_slice_new0 (GstRTCPPacket); + gst_rtcp_buffer_add_packet (&rtcp, GST_RTCP_TYPE_RR, rtcp_packet); + gst_rtcp_packet_rr_set_ssrc (rtcp_packet, ssrc); + g_slice_free (GstRTCPPacket, rtcp_packet); + + /* useful to make the rtcp buffer valid */ + rtcp_packet = g_slice_new0 (GstRTCPPacket); + gst_rtcp_buffer_add_packet (&rtcp, GST_RTCP_TYPE_APP, rtcp_packet); + g_slice_free (GstRTCPPacket, rtcp_packet); + gst_rtcp_buffer_unmap (&rtcp); + + return rtcp_buffer; +} + +static guint nb_ssrc_changes; +static guint ssrc_prev; + +static GstPadProbeReturn +rtpsession_sinkpad_probe (GstPad * pad, GstPadProbeInfo * info, + gpointer user_data) +{ + GstPadProbeReturn ret = GST_PAD_PROBE_OK; + + if (info->type == (GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_PUSH)) { + GstBuffer *buffer = GST_BUFFER (info->data); + GstRTPBuffer rtp = GST_RTP_BUFFER_INIT; + GstBuffer *rtcp_buffer = 0; + guint ssrc = 0; + + /* retrieve current ssrc */ + gst_rtp_buffer_map (buffer, GST_MAP_READ, &rtp); + ssrc = gst_rtp_buffer_get_ssrc (&rtp); + gst_rtp_buffer_unmap (&rtp); + + /* if not first buffer, check that our ssrc has changed */ + if (ssrc_prev != -1 && ssrc != ssrc_prev) + ++nb_ssrc_changes; + + /* update prev ssrc */ + ssrc_prev = ssrc; + + /* feint a collision on recv_rtcp_sink pad of gstrtpsession + * (note that after being marked as collied the rtpsession ignores + * all non bye packets) + */ + rtcp_buffer = create_rtcp_app (ssrc, nb_ssrc_changes); + + /* push collied packet on recv_rtcp_sink */ + gst_pad_push (srcpad, rtcp_buffer); + } + + return ret; +} + +static GstFlowReturn +fake_udp_sink_chain_func (GstPad * pad, GstObject * parent, GstBuffer * buffer) +{ + gst_buffer_unref (buffer); + return GST_FLOW_OK; +} + +/* This test build the pipeline audiotestsrc ! speexenc ! rtpspeexpay ! \ + * rtpsession ! fakesink + * It manually pushs buffer into rtpsession with same ssrc but different + * ip so that collision can be detected + * The test checks that the payloader change their ssrc + */ +GST_START_TEST (test_master_ssrc_collision) +{ + GstElement *bin, *src, *encoder, *rtppayloader, *rtpsession, *sink; + GstBus *bus = NULL; + gboolean res = FALSE; + GstSegment segment; + GstPad *sinkpad = NULL; + GstPad *rtcp_sinkpad = NULL; + GstPad *fake_udp_sinkpad = NULL; + GstPad *rtcp_srcpad = NULL; + GstStateChangeReturn state_res = GST_STATE_CHANGE_FAILURE; + + GST_INFO ("preparing test"); + + nb_ssrc_changes = 0; + ssrc_prev = -1; + + /* build pipeline */ + bin = gst_pipeline_new ("pipeline"); + bus = gst_element_get_bus (bin); + gst_bus_add_signal_watch_full (bus, G_PRIORITY_HIGH); + + src = gst_element_factory_make ("audiotestsrc", "src"); + g_object_set (src, "num-buffers", 5, NULL); + encoder = gst_element_factory_make ("speexenc", NULL); + rtppayloader = gst_element_factory_make ("rtpspeexpay", NULL); + g_object_set (rtppayloader, "pt", 96, NULL); + rtpsession = gst_element_factory_make ("rtpsession", NULL); + sink = gst_element_factory_make ("fakesink", "sink"); + gst_bin_add_many (GST_BIN (bin), src, encoder, rtppayloader, + rtpsession, sink, NULL); + + /* link elements */ + res = gst_element_link (src, encoder); + fail_unless (res == TRUE, NULL); + res = gst_element_link (encoder, rtppayloader); + fail_unless (res == TRUE, NULL); + res = gst_element_link_pads_full (rtppayloader, "src", + rtpsession, "send_rtp_sink", GST_PAD_LINK_CHECK_NOTHING); + fail_unless (res == TRUE, NULL); + res = gst_element_link_pads_full (rtpsession, "send_rtp_src", + sink, "sink", GST_PAD_LINK_CHECK_NOTHING); + fail_unless (res == TRUE, NULL); + + /* add probe on rtpsession sink pad to induce collision */ + sinkpad = gst_element_get_static_pad (rtpsession, "send_rtp_sink"); + gst_pad_add_probe (sinkpad, + (GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_PUSH), + (GstPadProbeCallback) rtpsession_sinkpad_probe, NULL, NULL); + gst_object_unref (sinkpad); + + /* setup rtcp link */ + srcpad = gst_pad_new_from_static_template (&srctemplate, "src"); + rtcp_sinkpad = gst_element_get_request_pad (rtpsession, "recv_rtcp_sink"); + fail_unless (gst_pad_link (srcpad, rtcp_sinkpad) == GST_PAD_LINK_OK, NULL); + gst_object_unref (rtcp_sinkpad); + res = gst_pad_set_active (srcpad, TRUE); + fail_if (res == FALSE); + res = + gst_pad_push_event (srcpad, + gst_event_new_stream_start ("my_rtcp_stream_id")); + fail_if (res == FALSE); + gst_segment_init (&segment, GST_FORMAT_TIME); + res = gst_pad_push_event (srcpad, gst_event_new_segment (&segment)); + fail_if (res == FALSE); + + fake_udp_sinkpad = gst_pad_new_from_static_template (&sinktemplate, "sink"); + gst_pad_set_chain_function (fake_udp_sinkpad, fake_udp_sink_chain_func); + rtcp_srcpad = gst_element_get_request_pad (rtpsession, "send_rtcp_src"); + fail_unless (gst_pad_link (rtcp_srcpad, fake_udp_sinkpad) == GST_PAD_LINK_OK, + NULL); + gst_object_unref (rtcp_srcpad); + res = gst_pad_set_active (fake_udp_sinkpad, TRUE); + fail_if (res == FALSE); + + /* connect messages */ + main_loop = g_main_loop_new (NULL, FALSE); + g_signal_connect (bus, "message::error", (GCallback) message_received, bin); + g_signal_connect (bus, "message::warning", (GCallback) message_received, bin); + g_signal_connect (bus, "message::eos", (GCallback) message_received, bin); + + state_res = gst_element_set_state (bin, GST_STATE_PLAYING); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + GST_INFO ("running main loop"); + g_main_loop_run (main_loop); + + state_res = gst_element_set_state (bin, GST_STATE_NULL); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + /* cleanup */ + gst_object_unref (srcpad); + gst_object_unref (fake_udp_sinkpad); + g_main_loop_unref (main_loop); + gst_bus_remove_signal_watch (bus); + gst_object_unref (bus); + gst_object_unref (bin); + + /* check results */ + fail_unless_equals_int (nb_ssrc_changes, 7); +} + +GST_END_TEST; + +static guint ssrc_before; +static guint ssrc_after; +static guint rtx_ssrc_before; +static guint rtx_ssrc_after; + +static GstPadProbeReturn +rtpsession_sinkpad_probe2 (GstPad * pad, GstPadProbeInfo * info, + gpointer user_data) +{ + GstPadProbeReturn ret = GST_PAD_PROBE_OK; + + if (info->type == (GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_PUSH)) { + GstBuffer *buffer = GST_BUFFER (info->data); + GstRTPBuffer rtp = GST_RTP_BUFFER_INIT; + guint payload_type = 0; + + static gint i = 0; + + /* retrieve current ssrc for retransmission stream only */ + gst_rtp_buffer_map (buffer, GST_MAP_READ, &rtp); + payload_type = gst_rtp_buffer_get_payload_type (&rtp); + if (payload_type == 99) { + if (i < 3) + rtx_ssrc_before = gst_rtp_buffer_get_ssrc (&rtp); + else + rtx_ssrc_after = gst_rtp_buffer_get_ssrc (&rtp); + } else { + /* ask to retransmit every packet */ + GstEvent *event = gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, + gst_structure_new ("GstRTPRetransmissionRequest", + "seqnum", G_TYPE_UINT, gst_rtp_buffer_get_seq (&rtp), + "ssrc", G_TYPE_UINT, gst_rtp_buffer_get_ssrc (&rtp), + NULL)); + gst_pad_push_event (pad, event); + + if (i < 3) + ssrc_before = gst_rtp_buffer_get_ssrc (&rtp); + else + ssrc_after = gst_rtp_buffer_get_ssrc (&rtp); + } + gst_rtp_buffer_unmap (&rtp); + + /* feint a collision on recv_rtcp_sink pad of gstrtpsession + * (note that after being marked as collied the rtpsession ignores + * all non bye packets) + */ + if (i == 2) { + GstBuffer *rtcp_buffer = create_rtcp_app (rtx_ssrc_before, 0); + + /* push collied packet on recv_rtcp_sink */ + gst_pad_push (srcpad, rtcp_buffer); + } + + ++i; + } + + return ret; +} + +/* This test build the pipeline audiotestsrc ! speexenc ! rtpspeexpay ! \ + * rtprtxsend ! rtpsession ! fakesink + * It manually pushs buffer into rtpsession with same ssrc than rtx stream + * but different ip so that collision can be detected + * The test checks that the rtx elements changes its ssrc whereas + * the payloader keeps its master ssrc + */ +GST_START_TEST (test_rtx_ssrc_collision) +{ + GstElement *bin, *src, *encoder, *rtppayloader, *rtprtxsend, *rtpsession, + *sink; + GstBus *bus = NULL; + gboolean res = FALSE; + GstSegment segment; + GstPad *sinkpad = NULL; + GstPad *rtcp_sinkpad = NULL; + GstPad *fake_udp_sinkpad = NULL; + GstPad *rtcp_srcpad = NULL; + GstStateChangeReturn state_res = GST_STATE_CHANGE_FAILURE; + GstStructure *pt_map; + + GST_INFO ("preparing test"); + + /* build pipeline */ + bin = gst_pipeline_new ("pipeline"); + bus = gst_element_get_bus (bin); + gst_bus_add_signal_watch_full (bus, G_PRIORITY_HIGH); + + src = gst_element_factory_make ("audiotestsrc", "src"); + g_object_set (src, "num-buffers", 5, NULL); + encoder = gst_element_factory_make ("speexenc", NULL); + rtppayloader = gst_element_factory_make ("rtpspeexpay", NULL); + g_object_set (rtppayloader, "pt", 96, NULL); + rtprtxsend = gst_element_factory_make ("rtprtxsend", NULL); + pt_map = gst_structure_new ("application/x-rtp-pt-map", + "96", G_TYPE_UINT, 99, NULL); + g_object_set (rtprtxsend, "payload-type-map", pt_map, NULL); + gst_structure_free (pt_map); + rtpsession = gst_element_factory_make ("rtpsession", NULL); + sink = gst_element_factory_make ("fakesink", "sink"); + gst_bin_add_many (GST_BIN (bin), src, encoder, rtppayloader, rtprtxsend, + rtpsession, sink, NULL); + + /* link elements */ + res = gst_element_link (src, encoder); + fail_unless (res == TRUE, NULL); + res = gst_element_link (encoder, rtppayloader); + fail_unless (res == TRUE, NULL); + res = gst_element_link (rtppayloader, rtprtxsend); + fail_unless (res == TRUE, NULL); + res = gst_element_link_pads_full (rtprtxsend, "src", + rtpsession, "send_rtp_sink", GST_PAD_LINK_CHECK_NOTHING); + fail_unless (res == TRUE, NULL); + res = gst_element_link_pads_full (rtpsession, "send_rtp_src", + sink, "sink", GST_PAD_LINK_CHECK_NOTHING); + fail_unless (res == TRUE, NULL); + + /* add probe on rtpsession sink pad to induce collision */ + sinkpad = gst_element_get_static_pad (rtpsession, "send_rtp_sink"); + gst_pad_add_probe (sinkpad, + (GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_PUSH), + (GstPadProbeCallback) rtpsession_sinkpad_probe2, NULL, NULL); + gst_object_unref (sinkpad); + + /* setup rtcp link */ + srcpad = gst_pad_new_from_static_template (&srctemplate, "src"); + rtcp_sinkpad = gst_element_get_request_pad (rtpsession, "recv_rtcp_sink"); + fail_unless (gst_pad_link (srcpad, rtcp_sinkpad) == GST_PAD_LINK_OK, NULL); + gst_object_unref (rtcp_sinkpad); + res = gst_pad_set_active (srcpad, TRUE); + fail_if (res == FALSE); + res = + gst_pad_push_event (srcpad, + gst_event_new_stream_start ("my_rtcp_stream_id")); + fail_if (res == FALSE); + gst_segment_init (&segment, GST_FORMAT_TIME); + res = gst_pad_push_event (srcpad, gst_event_new_segment (&segment)); + fail_if (res == FALSE); + + fake_udp_sinkpad = gst_pad_new_from_static_template (&sinktemplate, "sink"); + gst_pad_set_chain_function (fake_udp_sinkpad, fake_udp_sink_chain_func); + rtcp_srcpad = gst_element_get_request_pad (rtpsession, "send_rtcp_src"); + fail_unless (gst_pad_link (rtcp_srcpad, fake_udp_sinkpad) == GST_PAD_LINK_OK, + NULL); + gst_object_unref (rtcp_srcpad); + res = gst_pad_set_active (fake_udp_sinkpad, TRUE); + fail_if (res == FALSE); + + /* connect messages */ + main_loop = g_main_loop_new (NULL, FALSE); + g_signal_connect (bus, "message::error", (GCallback) message_received, bin); + g_signal_connect (bus, "message::warning", (GCallback) message_received, bin); + g_signal_connect (bus, "message::eos", (GCallback) message_received, bin); + + state_res = gst_element_set_state (bin, GST_STATE_PLAYING); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + GST_INFO ("running main loop"); + g_main_loop_run (main_loop); + + state_res = gst_element_set_state (bin, GST_STATE_NULL); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + /* cleanup */ + gst_object_unref (srcpad); + gst_object_unref (fake_udp_sinkpad); + g_main_loop_unref (main_loop); + gst_bus_remove_signal_watch (bus); + gst_object_unref (bus); + gst_object_unref (bin); + + /* check results */ + fail_if (rtx_ssrc_before == rtx_ssrc_after); + fail_if (ssrc_before != ssrc_after); +} + +GST_END_TEST; + +static Suite * +rtpcollision_suite (void) +{ + Suite *s = suite_create ("rtpcollision"); + TCase *tc_chain = tcase_create ("general"); + + tcase_set_timeout (tc_chain, 10); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_master_ssrc_collision); + tcase_add_test (tc_chain, test_rtx_ssrc_collision); + + return s; +} + +GST_CHECK_MAIN (rtpcollision); diff --git a/tests/check/elements/rtpjitterbuffer.c b/tests/check/elements/rtpjitterbuffer.c new file mode 100755 index 0000000..31999ef --- /dev/null +++ b/tests/check/elements/rtpjitterbuffer.c @@ -0,0 +1,1596 @@ +/* GStreamer + * + * Copyright (C) 2009 Nokia Corporation and its subsidary(-ies) + * contact: <stefan.kost@nokia.com> + * Copyright (C) 2012 Cisco Systems, Inc + * Authors: Kelley Rogers <kelro@cisco.com> + * Havard Graff <hgraff@cisco.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. + */ + +#include <gst/check/gstcheck.h> +#include <gst/check/gsttestclock.h> + +#include <gst/rtp/gstrtpbuffer.h> + +/* 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; +/* we also have a list of src buffers */ +static GList *inbuffers = NULL; +static gint num_dropped = 0; + +#define RTP_CAPS_STRING \ + "application/x-rtp, " \ + "media = (string)audio, " \ + "payload = (int) 0, " \ + "clock-rate = (int) 8000, " \ + "encoding-name = (string)PCMU" + +#define RTP_FRAME_SIZE 20 + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-rtp") + ); +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-rtp, " + "clock-rate = (int) [ 1, 2147483647 ]") + ); + +static void +buffer_dropped (gpointer data, GstMiniObject * obj) +{ + GST_DEBUG ("dropping buffer %p", obj); + num_dropped++; +} + +static GstElement * +setup_jitterbuffer (gint num_buffers) +{ + GstElement *jitterbuffer; + GstClock *clock; + GstBuffer *buffer; + GstCaps *caps; + /* a 20 sample audio block (2,5 ms) generated with + * gst-launch audiotestsrc wave=silence blocksize=40 num-buffers=3 ! + * "audio/x-raw,channels=1,rate=8000" ! mulawenc ! rtppcmupay ! + * fakesink dump=1 + */ + guint8 in[] = { /* first 4 bytes are rtp-header, next 4 bytes are timestamp */ + 0x80, 0x80, 0x1c, 0x24, 0x46, 0xcd, 0xb7, 0x11, 0x3c, 0x3a, 0x7c, 0x5b, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; + GstClockTime ts = G_GUINT64_CONSTANT (0); + GstClockTime tso = gst_util_uint64_scale (RTP_FRAME_SIZE, GST_SECOND, 8000); + /*guint latency = GST_TIME_AS_MSECONDS (num_buffers * tso); */ + gint i; + + GST_DEBUG ("setup_jitterbuffer"); + jitterbuffer = gst_check_setup_element ("rtpjitterbuffer"); + /* we need a clock here */ + clock = gst_system_clock_obtain (); + gst_element_set_clock (jitterbuffer, clock); + gst_object_unref (clock); + /* setup latency */ + /* latency would be 7 for 3 buffers here, default is 200 + g_object_set (G_OBJECT (jitterbuffer), "latency", latency, NULL); + GST_INFO_OBJECT (jitterbuffer, "set latency to %u ms", latency); + */ + + mysrcpad = gst_check_setup_src_pad (jitterbuffer, &srctemplate); + mysinkpad = gst_check_setup_sink_pad (jitterbuffer, &sinktemplate); + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + + /* create n buffers */ + caps = gst_caps_from_string (RTP_CAPS_STRING); + gst_check_setup_events (mysrcpad, jitterbuffer, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + + for (i = 0; i < num_buffers; i++) { + buffer = gst_buffer_new_and_alloc (sizeof (in)); + gst_buffer_fill (buffer, 0, in, sizeof (in)); + GST_BUFFER_DTS (buffer) = ts; + GST_BUFFER_PTS (buffer) = ts; + GST_BUFFER_DURATION (buffer) = tso; + gst_mini_object_weak_ref (GST_MINI_OBJECT (buffer), buffer_dropped, NULL); + GST_DEBUG ("created buffer: %p", buffer); + + if (!i) + GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT); + + inbuffers = g_list_append (inbuffers, buffer); + + /* hackish way to update the rtp header */ + in[1] = 0x00; + in[3]++; /* seqnumber */ + in[7] += RTP_FRAME_SIZE; /* inc. timestamp with framesize */ + ts += tso; + } + num_dropped = 0; + + return jitterbuffer; +} + +static GstStateChangeReturn +start_jitterbuffer (GstElement * jitterbuffer) +{ + GstStateChangeReturn ret; + GstClockTime now; + GstClock *clock; + + clock = gst_element_get_clock (jitterbuffer); + now = gst_clock_get_time (clock); + gst_object_unref (clock); + + gst_element_set_base_time (jitterbuffer, now); + ret = gst_element_set_state (jitterbuffer, GST_STATE_PLAYING); + + return ret; +} + +static void +cleanup_jitterbuffer (GstElement * jitterbuffer) +{ + GST_DEBUG ("cleanup_jitterbuffer"); + + g_list_foreach (buffers, (GFunc) gst_mini_object_unref, NULL); + g_list_free (buffers); + buffers = NULL; + + g_list_free (inbuffers); + inbuffers = NULL; + + gst_pad_set_active (mysrcpad, FALSE); + gst_pad_set_active (mysinkpad, FALSE); + gst_check_teardown_src_pad (jitterbuffer); + gst_check_teardown_sink_pad (jitterbuffer); + gst_check_teardown_element (jitterbuffer); +} + +static void +check_jitterbuffer_results (GstElement * jitterbuffer, gint num_buffers) +{ + GstBuffer *buffer; + GList *node; + GstClockTime ts = G_GUINT64_CONSTANT (0); + GstClockTime tso = gst_util_uint64_scale (RTP_FRAME_SIZE, GST_SECOND, 8000); + GstMapInfo map; + guint16 prev_sn = 0, cur_sn; + guint32 prev_ts = 0, cur_ts; + + /* sleep for twice the latency */ + g_usleep (400 * 1000); + + GST_INFO ("of %d buffer %d/%d received/dropped", num_buffers, + g_list_length (buffers), num_dropped); + /* if this fails, not all buffers have been processed */ + fail_unless_equals_int ((g_list_length (buffers) + num_dropped), num_buffers); + + /* check the buffer list */ + fail_unless_equals_int (g_list_length (buffers), num_buffers); + for (node = buffers; node; node = g_list_next (node)) { + fail_if ((buffer = (GstBuffer *) node->data) == NULL); + fail_if (GST_BUFFER_PTS (buffer) != ts); + fail_if (GST_BUFFER_DTS (buffer) != ts); + gst_buffer_map (buffer, &map, GST_MAP_READ); + cur_sn = ((guint16) map.data[2] << 8) | map.data[3]; + cur_ts = ((guint32) map.data[4] << 24) | ((guint32) map.data[5] << 16) | + ((guint32) map.data[6] << 8) | map.data[7]; + gst_buffer_unmap (buffer, &map); + + if (node != buffers) { + fail_unless (cur_sn > prev_sn); + fail_unless (cur_ts > prev_ts); + + prev_sn = cur_sn; + prev_ts = cur_ts; + } + ts += tso; + } +} + +GST_START_TEST (test_push_forward_seq) +{ + GstElement *jitterbuffer; + const guint num_buffers = 3; + GstBuffer *buffer; + GList *node; + + jitterbuffer = setup_jitterbuffer (num_buffers); + fail_unless (start_jitterbuffer (jitterbuffer) + == GST_STATE_CHANGE_SUCCESS, "could not set to playing"); + + /* push buffers: 0,1,2, */ + for (node = inbuffers; node; node = g_list_next (node)) { + buffer = (GstBuffer *) node->data; + fail_unless (gst_pad_push (mysrcpad, buffer) == GST_FLOW_OK); + } + + /* check the buffer list */ + check_jitterbuffer_results (jitterbuffer, num_buffers); + + /* cleanup */ + cleanup_jitterbuffer (jitterbuffer); +} + +GST_END_TEST; + +GST_START_TEST (test_push_backward_seq) +{ + GstElement *jitterbuffer; + const guint num_buffers = 4; + GstBuffer *buffer; + GList *node; + + jitterbuffer = setup_jitterbuffer (num_buffers); + fail_unless (start_jitterbuffer (jitterbuffer) + == GST_STATE_CHANGE_SUCCESS, "could not set to playing"); + + /* push buffers: 0,3,2,1 */ + buffer = (GstBuffer *) inbuffers->data; + fail_unless (gst_pad_push (mysrcpad, buffer) == GST_FLOW_OK); + for (node = g_list_last (inbuffers); node != inbuffers; + node = g_list_previous (node)) { + buffer = (GstBuffer *) node->data; + fail_unless (gst_pad_push (mysrcpad, buffer) == GST_FLOW_OK); + } + + /* check the buffer list */ + check_jitterbuffer_results (jitterbuffer, num_buffers); + + /* cleanup */ + cleanup_jitterbuffer (jitterbuffer); +} + +GST_END_TEST; + +GST_START_TEST (test_push_unordered) +{ + GstElement *jitterbuffer; + const guint num_buffers = 4; + GstBuffer *buffer; + + jitterbuffer = setup_jitterbuffer (num_buffers); + fail_unless (start_jitterbuffer (jitterbuffer) + == GST_STATE_CHANGE_SUCCESS, "could not set to playing"); + + /* push buffers; 0,2,1,3 */ + buffer = (GstBuffer *) inbuffers->data; + fail_unless (gst_pad_push (mysrcpad, buffer) == GST_FLOW_OK); + buffer = g_list_nth_data (inbuffers, 2); + fail_unless (gst_pad_push (mysrcpad, buffer) == GST_FLOW_OK); + buffer = g_list_nth_data (inbuffers, 1); + fail_unless (gst_pad_push (mysrcpad, buffer) == GST_FLOW_OK); + buffer = g_list_nth_data (inbuffers, 3); + fail_unless (gst_pad_push (mysrcpad, buffer) == GST_FLOW_OK); + + /* check the buffer list */ + check_jitterbuffer_results (jitterbuffer, num_buffers); + + /* cleanup */ + cleanup_jitterbuffer (jitterbuffer); +} + +GST_END_TEST; + +GST_START_TEST (test_basetime) +{ + GstElement *jitterbuffer; + const guint num_buffers = 3; + GstBuffer *buffer; + GList *node; + GstClockTime tso = gst_util_uint64_scale (RTP_FRAME_SIZE, GST_SECOND, 8000); + + jitterbuffer = setup_jitterbuffer (num_buffers); + fail_unless (start_jitterbuffer (jitterbuffer) + == GST_STATE_CHANGE_SUCCESS, "could not set to playing"); + + /* push buffers: 2,1,0 */ + for (node = g_list_last (inbuffers); node; node = g_list_previous (node)) { + buffer = (GstBuffer *) node->data; + fail_unless (gst_pad_push (mysrcpad, buffer) == GST_FLOW_OK); + } + + /* sleep for twice the latency */ + g_usleep (400 * 1000); + + /* if this fails, not all buffers have been processed */ + fail_unless_equals_int ((g_list_length (buffers) + num_dropped), num_buffers); + + buffer = (GstBuffer *) buffers->data; + fail_unless (GST_BUFFER_DTS (buffer) != (num_buffers * tso)); + fail_unless (GST_BUFFER_PTS (buffer) != (num_buffers * tso)); + + /* cleanup */ + cleanup_jitterbuffer (jitterbuffer); +} + +GST_END_TEST; + +static GstCaps * +request_pt_map (GstElement * jitterbuffer, guint pt) +{ + fail_unless (pt == 0); + + return gst_caps_from_string (RTP_CAPS_STRING); +} + +GST_START_TEST (test_clear_pt_map) +{ + GstElement *jitterbuffer; + const guint num_buffers = 10; + gint i; + GstBuffer *buffer; + GList *node; + + jitterbuffer = setup_jitterbuffer (num_buffers); + fail_unless (start_jitterbuffer (jitterbuffer) + == GST_STATE_CHANGE_SUCCESS, "could not set to playing"); + + g_signal_connect (jitterbuffer, "request-pt-map", (GCallback) + request_pt_map, NULL); + + /* push buffers: 0,1,2, */ + for (node = inbuffers, i = 0; node && i < 3; node = g_list_next (node), i++) { + buffer = (GstBuffer *) node->data; + fail_unless (gst_pad_push (mysrcpad, buffer) == GST_FLOW_OK); + } + + g_usleep (400 * 1000); + + g_signal_emit_by_name (jitterbuffer, "clear-pt-map", NULL); + + for (; node && i < 10; node = g_list_next (node), i++) { + buffer = (GstBuffer *) node->data; + fail_unless (gst_pad_push (mysrcpad, buffer) == GST_FLOW_OK); + } + + /* check the buffer list */ + check_jitterbuffer_results (jitterbuffer, num_buffers); + + /* cleanup */ + cleanup_jitterbuffer (jitterbuffer); +} + +GST_END_TEST; +static const guint payload_size = 160; +static const guint clock_rate = 8000; +static const guint pcmu_payload_type = 0; +static const guint test_ssrc = 0x01BADBAD; + +typedef struct +{ + GstElement *jitter_buffer; + GstPad *test_sink_pad, *test_src_pad; + GstClock *clock; + GAsyncQueue *buf_queue; + GAsyncQueue *sink_event_queue; + GAsyncQueue *src_event_queue; + gint lost_event_count; + gint rtx_event_count; +} TestData; + +static GstCaps * +generate_caps (void) +{ + return gst_caps_new_simple ("application/x-rtp", + "media", G_TYPE_STRING, "audio", + "clock-rate", G_TYPE_INT, clock_rate, + "encoding-name", G_TYPE_STRING, "PCMU", + "payload", G_TYPE_INT, pcmu_payload_type, + "ssrc", G_TYPE_UINT, test_ssrc, NULL); +} + +static GstBuffer * +generate_test_buffer (GstClockTime gst_ts, + gboolean marker_bit, guint seq_num, guint32 rtp_ts) +{ + GstBuffer *buf; + guint8 *payload; + guint i; + GstRTPBuffer rtp = GST_RTP_BUFFER_INIT; + + buf = gst_rtp_buffer_new_allocate (payload_size, 0, 0); + GST_BUFFER_DTS (buf) = gst_ts; + GST_BUFFER_PTS (buf) = gst_ts; + + gst_rtp_buffer_map (buf, GST_MAP_READWRITE, &rtp); + gst_rtp_buffer_set_payload_type (&rtp, pcmu_payload_type); + gst_rtp_buffer_set_marker (&rtp, marker_bit); + gst_rtp_buffer_set_seq (&rtp, seq_num); + gst_rtp_buffer_set_timestamp (&rtp, rtp_ts); + gst_rtp_buffer_set_ssrc (&rtp, test_ssrc); + + payload = gst_rtp_buffer_get_payload (&rtp); + for (i = 0; i < payload_size; i++) + payload[i] = 0xff; + + gst_rtp_buffer_unmap (&rtp); + + return buf; +} + +static GstFlowReturn +test_sink_pad_chain_cb (GstPad * pad, GstObject * parent, GstBuffer * buffer) +{ + TestData *data = gst_pad_get_element_private (pad); + g_async_queue_push (data->buf_queue, buffer); + return GST_FLOW_OK; +} + +static gboolean +test_sink_pad_event_cb (GstPad * pad, GstObject * parent, GstEvent * event) +{ + TestData *data = gst_pad_get_element_private (pad); + const GstStructure *structure = gst_event_get_structure (event); + + GST_DEBUG ("got event %" GST_PTR_FORMAT, event); + + if (strcmp (gst_structure_get_name (structure), "GstRTPPacketLost") == 0) { + data->lost_event_count++; + GST_DEBUG ("lost event count %d", data->lost_event_count); + } + + g_async_queue_push (data->sink_event_queue, event); + return TRUE; +} + +static gboolean +test_src_pad_event_cb (GstPad * pad, GstObject * parent, GstEvent * event) +{ + TestData *data = gst_pad_get_element_private (pad); + const GstStructure *structure = gst_event_get_structure (event); + + GST_DEBUG ("got event %" GST_PTR_FORMAT, event); + + if (structure + && strcmp (gst_structure_get_name (structure), + "GstRTPRetransmissionRequest") == 0) { + data->rtx_event_count++; + GST_DEBUG ("rtx event count %d", data->rtx_event_count); + } + + g_async_queue_push (data->src_event_queue, event); + return TRUE; +} + +static void +setup_testharness (TestData * data) +{ + GstPad *jb_sink_pad, *jb_src_pad; + GstSegment seg; + GstMiniObject *obj; + GstCaps *caps; + + /* create the testclock */ + data->clock = gst_test_clock_new (); + g_assert (data->clock); + gst_test_clock_set_time (GST_TEST_CLOCK (data->clock), 0); + + /* rig up the jitter buffer */ + data->jitter_buffer = gst_element_factory_make ("rtpjitterbuffer", NULL); + g_assert (data->jitter_buffer); + gst_element_set_clock (data->jitter_buffer, data->clock); + g_object_set (data->jitter_buffer, "do-lost", TRUE, NULL); + g_assert_cmpint (gst_element_set_state (data->jitter_buffer, + GST_STATE_PLAYING), !=, GST_STATE_CHANGE_FAILURE); + + /* set up the buf and event queues */ + data->buf_queue = + g_async_queue_new_full ((GDestroyNotify) gst_mini_object_unref); + data->sink_event_queue = + g_async_queue_new_full ((GDestroyNotify) gst_mini_object_unref); + data->src_event_queue = + g_async_queue_new_full ((GDestroyNotify) gst_mini_object_unref); + + data->lost_event_count = 0; + data->rtx_event_count = 0; + + /* link in the test source-pad */ + data->test_src_pad = gst_pad_new ("src", GST_PAD_SRC); + gst_pad_set_element_private (data->test_src_pad, data); + gst_pad_set_event_function (data->test_src_pad, test_src_pad_event_cb); + jb_sink_pad = gst_element_get_static_pad (data->jitter_buffer, "sink"); + g_assert_cmpint (gst_pad_link (data->test_src_pad, jb_sink_pad), ==, + GST_PAD_LINK_OK); + gst_object_unref (jb_sink_pad); + + /* link in the test sink-pad */ + data->test_sink_pad = gst_pad_new ("sink", GST_PAD_SINK); + gst_pad_set_element_private (data->test_sink_pad, data); + caps = generate_caps (); + gst_pad_set_caps (data->test_sink_pad, caps); + gst_pad_set_chain_function (data->test_sink_pad, test_sink_pad_chain_cb); + gst_pad_set_event_function (data->test_sink_pad, test_sink_pad_event_cb); + jb_src_pad = gst_element_get_static_pad (data->jitter_buffer, "src"); + g_assert_cmpint (gst_pad_link (jb_src_pad, data->test_sink_pad), ==, + GST_PAD_LINK_OK); + gst_object_unref (jb_src_pad); + + g_assert (gst_pad_set_active (data->test_src_pad, TRUE)); + g_assert (gst_pad_set_active (data->test_sink_pad, TRUE)); + + gst_segment_init (&seg, GST_FORMAT_TIME); + + gst_pad_push_event (data->test_src_pad, + gst_event_new_stream_start ("stream0")); + gst_pad_set_caps (data->test_src_pad, caps); + gst_pad_push_event (data->test_src_pad, gst_event_new_segment (&seg)); + gst_caps_unref (caps); + + obj = g_async_queue_pop (data->sink_event_queue); + gst_mini_object_unref (obj); + obj = g_async_queue_pop (data->sink_event_queue); + gst_mini_object_unref (obj); + obj = g_async_queue_pop (data->sink_event_queue); + gst_mini_object_unref (obj); +} + +static void +destroy_testharness (TestData * data) +{ + /* clean up */ + g_assert_cmpint (gst_element_set_state (data->jitter_buffer, GST_STATE_NULL), + ==, GST_STATE_CHANGE_SUCCESS); + gst_object_unref (data->jitter_buffer); + data->jitter_buffer = NULL; + + gst_object_unref (data->test_src_pad); + data->test_src_pad = NULL; + + gst_object_unref (data->test_sink_pad); + data->test_sink_pad = NULL; + + gst_object_unref (data->clock); + data->clock = NULL; + + g_async_queue_unref (data->buf_queue); + data->buf_queue = NULL; + + g_async_queue_unref (data->sink_event_queue); + data->sink_event_queue = NULL; + g_async_queue_unref (data->src_event_queue); + data->src_event_queue = NULL; + + data->lost_event_count = 0; +} + +static void +verify_lost_event (GstEvent * event, guint32 expected_seqnum, + GstClockTime expected_timestamp, GstClockTime expected_duration, + gboolean expected_late) +{ + const GstStructure *s = gst_event_get_structure (event); + const GValue *value; + guint32 seqnum; + GstClockTime timestamp; + GstClockTime duration; + gboolean late; + + g_assert (gst_structure_get_uint (s, "seqnum", &seqnum)); + + value = gst_structure_get_value (s, "timestamp"); + g_assert (value && G_VALUE_HOLDS_UINT64 (value)); + timestamp = g_value_get_uint64 (value); + + value = gst_structure_get_value (s, "duration"); + g_assert (value && G_VALUE_HOLDS_UINT64 (value)); + duration = g_value_get_uint64 (value); + + g_assert (gst_structure_get_boolean (s, "late", &late)); + + g_assert_cmpint (seqnum, ==, expected_seqnum); + g_assert_cmpint (timestamp, ==, expected_timestamp); + g_assert_cmpint (duration, ==, expected_duration); + g_assert (late == expected_late); + + gst_event_unref (event); +} + +static void +verify_rtx_event (GstEvent * event, guint32 expected_seqnum, + GstClockTime expected_timestamp, guint expected_delay, + GstClockTime expected_spacing) +{ + const GstStructure *s = gst_event_get_structure (event); + const GValue *value; + guint32 seqnum; + GstClockTime timestamp, spacing; + guint delay; + + g_assert (gst_structure_get_uint (s, "seqnum", &seqnum)); + + value = gst_structure_get_value (s, "running-time"); + g_assert (value && G_VALUE_HOLDS_UINT64 (value)); + timestamp = g_value_get_uint64 (value); + + value = gst_structure_get_value (s, "delay"); + g_assert (value && G_VALUE_HOLDS_UINT (value)); + delay = g_value_get_uint (value); + + value = gst_structure_get_value (s, "packet-spacing"); + g_assert (value && G_VALUE_HOLDS_UINT64 (value)); + spacing = g_value_get_uint64 (value); + + g_assert_cmpint (seqnum, ==, expected_seqnum); + g_assert_cmpint (timestamp, ==, expected_timestamp); + g_assert_cmpint (delay, ==, expected_delay); + g_assert_cmpint (spacing, ==, expected_spacing); + + gst_event_unref (event); +} + +GST_START_TEST (test_only_one_lost_event_on_large_gaps) +{ + TestData data; + GstClockID id, test_id; + GstBuffer *in_buf, *out_buf; + GstEvent *out_event; + gint jb_latency_ms = 200; + guint buffer_size_ms = (payload_size * 1000) / clock_rate; + GstRTPBuffer rtp = GST_RTP_BUFFER_INIT; + + setup_testharness (&data); + + g_object_set (data.jitter_buffer, "latency", jb_latency_ms, NULL); + /* push the first buffer in */ + in_buf = generate_test_buffer (0 * GST_MSECOND, TRUE, 0, 0); + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), 0); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + + /* wait for the first buffer to be synced to timestamp + latency */ + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (data.clock), &id); + + /* increase the time to timestamp + latency and release the wait */ + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), + jb_latency_ms * GST_MSECOND); + test_id = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (data.clock)); + g_assert (test_id == id); + gst_clock_id_unref (test_id); + gst_clock_id_unref (id); + + /* check for the buffer coming out that was pushed in */ + out_buf = g_async_queue_pop (data.buf_queue); + g_assert (out_buf != NULL); + g_assert_cmpint (GST_BUFFER_DTS (out_buf), ==, 0); + g_assert_cmpint (GST_BUFFER_PTS (out_buf), ==, 0); + gst_buffer_unref (out_buf); + + /* move time ahead 10 seconds */ + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), 10 * GST_SECOND); + + /* wait a bit */ + g_usleep (G_USEC_PER_SEC / 10); + + /* check that no buffers have been pushed out and no pending waits */ + g_assert_cmpint (g_async_queue_length (data.buf_queue), ==, 0); + g_assert (gst_test_clock_peek_next_pending_id (GST_TEST_CLOCK (data.clock), + &id) == FALSE); + + /* a buffer now arrives perfectly on time */ + in_buf = generate_test_buffer (10 * GST_SECOND, FALSE, 500, 500 * 160); + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), 10 * GST_SECOND); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + + /* release the wait */ + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (data.clock), &id); + gst_test_clock_advance_time (GST_TEST_CLOCK (data.clock), GST_MSECOND * 20); + test_id = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (data.clock)); + g_assert (id == test_id); + gst_clock_id_unref (test_id); + gst_clock_id_unref (id); + + /* we should now receive a packet-lost-event for buffers 1 through 489 */ + out_event = g_async_queue_pop (data.sink_event_queue); + g_assert (out_event != NULL); + g_assert_cmpint (data.lost_event_count, ==, 1); + verify_lost_event (out_event, 1, 1 * GST_MSECOND * 20, GST_MSECOND * 20 * 490, + TRUE); + + /* churn through sync_times until the new buffer gets pushed out */ + while (g_async_queue_length (data.buf_queue) < 1) { + if (gst_test_clock_peek_next_pending_id (GST_TEST_CLOCK (data.clock), &id)) { + GstClockTime t = gst_clock_id_get_time (id); + if (t > gst_clock_get_time (data.clock)) { + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), t); + } + test_id = + gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (data.clock)); + gst_clock_id_unref (test_id); + gst_clock_id_unref (id); + } + } + + out_buf = g_async_queue_pop (data.buf_queue); + g_assert (out_buf != NULL); + g_assert (GST_BUFFER_FLAG_IS_SET (out_buf, GST_BUFFER_FLAG_DISCONT)); + gst_rtp_buffer_map (out_buf, GST_MAP_READ, &rtp); + g_assert_cmpint (gst_rtp_buffer_get_seq (&rtp), ==, 500); + gst_rtp_buffer_unmap (&rtp); + g_assert_cmpint (GST_BUFFER_DTS (out_buf), ==, (10 * GST_SECOND)); + g_assert_cmpint (GST_BUFFER_PTS (out_buf), ==, (10 * GST_SECOND)); + gst_buffer_unref (out_buf); + + /* we get as many lost events as the the number of buffers the jitterbuffer + * is able to wait for (+ the one we already got) */ + g_assert_cmpint (data.lost_event_count, ==, jb_latency_ms / buffer_size_ms); + + destroy_testharness (&data); +} + +GST_END_TEST; + +GST_START_TEST (test_two_lost_one_arrives_in_time) +{ + TestData data; + GstClockID id, test_id; + GstBuffer *in_buf, *out_buf; + GstEvent *out_event; + gint jb_latency_ms = 100; + GstClockTime buffer_time, now; + gint b; + GstRTPBuffer rtp = GST_RTP_BUFFER_INIT; + + setup_testharness (&data); + + g_object_set (data.jitter_buffer, "latency", jb_latency_ms, NULL); + + /* push the first buffer in */ + in_buf = generate_test_buffer (0 * GST_MSECOND, TRUE, 0, 0); + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), 0); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (data.clock), &id); + now = jb_latency_ms * GST_MSECOND; + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), now); + test_id = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (data.clock)); + g_assert (test_id == id); + gst_clock_id_unref (test_id); + gst_clock_id_unref (id); + out_buf = g_async_queue_pop (data.buf_queue); + g_assert (out_buf != NULL); + + /* push some buffers arriving in perfect time! */ + for (b = 1; b < 3; b++) { + buffer_time = b * GST_MSECOND * 20; + in_buf = generate_test_buffer (buffer_time, TRUE, b, b * 160); + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), now + buffer_time); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + gst_buffer_unref (out_buf); + + /* check for the buffer coming out that was pushed in */ + out_buf = g_async_queue_pop (data.buf_queue); + g_assert (out_buf != NULL); + g_assert_cmpint (GST_BUFFER_DTS (out_buf), ==, buffer_time); + g_assert_cmpint (GST_BUFFER_PTS (out_buf), ==, buffer_time); + } + gst_buffer_unref (out_buf); + + /* hop over 2 packets and make another one (gap of 2) */ + b = 5; + buffer_time = b * GST_MSECOND * 20; + in_buf = generate_test_buffer (buffer_time, TRUE, b, b * 160); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + + /* verify that the jitterbuffer now wait for the latest moment it can push */ + /* the first lost buffer (buffer 3) out on (buffer-timestamp (60) + latency (10) = 70) */ + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (data.clock), &id); + g_assert_cmpint (gst_clock_id_get_time (id), ==, + (3 * GST_MSECOND * 20) + (jb_latency_ms * GST_MSECOND)); + + /* let the time expire... */ + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), + gst_clock_id_get_time (id)); + test_id = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (data.clock)); + g_assert (test_id == id); + gst_clock_id_unref (test_id); + gst_clock_id_unref (id); + + /* we should now receive a packet-lost-event for buffer 3 */ + out_event = g_async_queue_pop (data.sink_event_queue); + g_assert (out_event != NULL); + g_assert_cmpint (data.lost_event_count, ==, 1); + verify_lost_event (out_event, 3, 3 * GST_MSECOND * 20, GST_MSECOND * 20, + FALSE); + + /* buffer 4 now arrives just in time (time is 70, buffer 4 expires at 90) */ + b = 4; + buffer_time = b * GST_MSECOND * 20; + in_buf = generate_test_buffer (buffer_time, TRUE, b, b * 160); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + + /* verify that buffer 4 made it through! */ + out_buf = g_async_queue_pop (data.buf_queue); + g_assert (out_buf != NULL); + g_assert (GST_BUFFER_FLAG_IS_SET (out_buf, GST_BUFFER_FLAG_DISCONT)); + gst_rtp_buffer_map (out_buf, GST_MAP_READ, &rtp); + g_assert_cmpint (gst_rtp_buffer_get_seq (&rtp), ==, 4); + gst_rtp_buffer_unmap (&rtp); + gst_buffer_unref (out_buf); + + /* and see that buffer 5 now arrives in a normal fashion */ + out_buf = g_async_queue_pop (data.buf_queue); + g_assert (out_buf != NULL); + g_assert (!GST_BUFFER_FLAG_IS_SET (out_buf, GST_BUFFER_FLAG_DISCONT)); + gst_rtp_buffer_map (out_buf, GST_MAP_READ, &rtp); + g_assert_cmpint (gst_rtp_buffer_get_seq (&rtp), ==, 5); + gst_rtp_buffer_unmap (&rtp); + gst_buffer_unref (out_buf); + + /* should still have only seen 1 packet lost event */ + g_assert_cmpint (data.lost_event_count, ==, 1); + + destroy_testharness (&data); +} + +GST_END_TEST; + +GST_START_TEST (test_late_packets_still_makes_lost_events) +{ + TestData data; + GstClockID id, test_id; + GstBuffer *in_buf, *out_buf; + GstEvent *out_event; + gint jb_latency_ms = 10; + GstClockTime buffer_time; + gint b; + GstRTPBuffer rtp = GST_RTP_BUFFER_INIT; + + setup_testharness (&data); + + g_object_set (data.jitter_buffer, "latency", jb_latency_ms, NULL); + + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), 10 * GST_SECOND); + + /* push the first buffer in */ + in_buf = generate_test_buffer (0 * GST_MSECOND, TRUE, 0, 0); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (data.clock), &id); + test_id = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (data.clock)); + g_assert (test_id == id); + gst_clock_id_unref (id); + gst_clock_id_unref (test_id); + out_buf = g_async_queue_pop (data.buf_queue); + g_assert (out_buf != NULL); + + /* push some buffers in! */ + for (b = 1; b < 3; b++) { + buffer_time = b * GST_MSECOND * 20; + in_buf = generate_test_buffer (buffer_time, TRUE, b, b * 160); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + gst_buffer_unref (out_buf); + + /* check for the buffer coming out that was pushed in */ + out_buf = g_async_queue_pop (data.buf_queue); + g_assert (out_buf != NULL); + g_assert_cmpint (GST_BUFFER_DTS (out_buf), ==, buffer_time); + g_assert_cmpint (GST_BUFFER_PTS (out_buf), ==, buffer_time); + } + gst_buffer_unref (out_buf); + + /* hop over 2 packets and make another one (gap of 2) */ + b = 5; + buffer_time = b * GST_MSECOND * 20; + in_buf = generate_test_buffer (buffer_time, TRUE, b, b * 160); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + + /* we should now receive a packet-lost-event for buffer 3 and 4 */ + out_event = g_async_queue_pop (data.sink_event_queue); + g_assert (out_event != NULL); + g_assert_cmpint (data.lost_event_count, ==, 1); + verify_lost_event (out_event, 3, 3 * GST_MSECOND * 20, GST_MSECOND * 20 * 2, + TRUE); + + /* verify that buffer 5 made it through! */ + out_buf = g_async_queue_pop (data.buf_queue); + g_assert (out_buf != NULL); + g_assert (GST_BUFFER_FLAG_IS_SET (out_buf, GST_BUFFER_FLAG_DISCONT)); + gst_rtp_buffer_map (out_buf, GST_MAP_READ, &rtp); + g_assert_cmpint (gst_rtp_buffer_get_seq (&rtp), ==, 5); + gst_rtp_buffer_unmap (&rtp); + gst_buffer_unref (out_buf); + + /* should still have only seen 1 packet lost event */ + g_assert_cmpint (data.lost_event_count, ==, 1); + + destroy_testharness (&data); +} + +GST_END_TEST; + +GST_START_TEST (test_all_packets_are_timestamped_zero) +{ + TestData data; + GstClockID id, test_id; + GstBuffer *in_buf, *out_buf; + GstEvent *out_event; + gint jb_latency_ms = 10; + gint b; + GstRTPBuffer rtp = GST_RTP_BUFFER_INIT; + + setup_testharness (&data); + + g_object_set (data.jitter_buffer, "latency", jb_latency_ms, NULL); + + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), 10 * GST_SECOND); + + /* push the first buffer in */ + in_buf = generate_test_buffer (0 * GST_MSECOND, TRUE, 0, 0); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (data.clock), &id); + test_id = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (data.clock)); + g_assert (test_id == id); + gst_clock_id_unref (test_id); + gst_clock_id_unref (id); + out_buf = g_async_queue_pop (data.buf_queue); + g_assert (out_buf != NULL); + + /* push some buffers in! */ + for (b = 1; b < 3; b++) { + in_buf = generate_test_buffer (0, TRUE, b, 0); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + gst_buffer_unref (out_buf); + + /* check for the buffer coming out that was pushed in */ + out_buf = g_async_queue_pop (data.buf_queue); + g_assert (out_buf != NULL); + g_assert_cmpint (GST_BUFFER_DTS (out_buf), ==, 0); + g_assert_cmpint (GST_BUFFER_PTS (out_buf), ==, 0); + } + gst_buffer_unref (out_buf); + + /* hop over 2 packets and make another one (gap of 2) */ + b = 5; + in_buf = generate_test_buffer (0, TRUE, b, 0); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + + /* we should now receive a packet-lost-event for buffer 3 and 4 */ + out_event = g_async_queue_pop (data.sink_event_queue); + g_assert (out_event != NULL); + verify_lost_event (out_event, 3, 0, 0, FALSE); + + out_event = g_async_queue_pop (data.sink_event_queue); + g_assert (out_event != NULL); + verify_lost_event (out_event, 4, 0, 0, FALSE); + + g_assert_cmpint (data.lost_event_count, ==, 2); + + /* verify that buffer 5 made it through! */ + out_buf = g_async_queue_pop (data.buf_queue); + g_assert (out_buf != NULL); + g_assert (GST_BUFFER_FLAG_IS_SET (out_buf, GST_BUFFER_FLAG_DISCONT)); + gst_rtp_buffer_map (out_buf, GST_MAP_READ, &rtp); + g_assert_cmpint (gst_rtp_buffer_get_seq (&rtp), ==, 5); + gst_rtp_buffer_unmap (&rtp); + gst_buffer_unref (out_buf); + + /* should still have only seen 2 packet lost events */ + g_assert_cmpint (data.lost_event_count, ==, 2); + + destroy_testharness (&data); +} + +GST_END_TEST; + +GST_START_TEST (test_rtx_expected_next) +{ + TestData data; + GstClockID id, tid; + GstBuffer *in_buf, *out_buf; + GstEvent *out_event; + gint jb_latency_ms = 200; + + setup_testharness (&data); + g_object_set (data.jitter_buffer, "do-retransmission", TRUE, NULL); + g_object_set (data.jitter_buffer, "latency", jb_latency_ms, NULL); + g_object_set (data.jitter_buffer, "rtx-retry-period", 120, NULL); + + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), 0); + + /* push the first buffer in */ + in_buf = generate_test_buffer (0 * GST_MSECOND, TRUE, 0, 0); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), 20 * GST_MSECOND); + + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (data.clock), &id); + gst_clock_id_unref (id); + + /* put second buffer, the jitterbuffer should now know that the packet spacing + * is 20ms and should ask for retransmission of seqnum 2 in 20ms */ + in_buf = generate_test_buffer (20 * GST_MSECOND, TRUE, 1, 160); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (data.clock), &id); + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), 60 * GST_MSECOND); + tid = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (data.clock)); + g_assert (tid == id); + gst_clock_id_unref (tid); + gst_clock_id_unref (id); + + out_event = g_async_queue_pop (data.src_event_queue); + g_assert (out_event != NULL); + verify_rtx_event (out_event, 2, 40 * GST_MSECOND, 20, 20 * GST_MSECOND); + + /* now we wait for the next timeout */ + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (data.clock), &id); + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), 100 * GST_MSECOND); + tid = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (data.clock)); + g_assert (id == tid); + gst_clock_id_unref (tid); + gst_clock_id_unref (id); + + out_event = g_async_queue_pop (data.src_event_queue); + g_assert (out_event != NULL); + verify_rtx_event (out_event, 2, 40 * GST_MSECOND, 60, 20 * GST_MSECOND); + + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (data.clock), &id); + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), 140 * GST_MSECOND); + tid = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (data.clock)); + g_assert (id == tid); + gst_clock_id_unref (tid); + gst_clock_id_unref (id); + + out_event = g_async_queue_pop (data.src_event_queue); + g_assert (out_event != NULL); + verify_rtx_event (out_event, 2, 40 * GST_MSECOND, 100, 20 * GST_MSECOND); + + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (data.clock), &id); + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), 200 * GST_MSECOND); + tid = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (data.clock)); + g_assert (id == tid); + gst_clock_id_unref (tid); + gst_clock_id_unref (id); + + out_buf = g_async_queue_pop (data.buf_queue); + g_assert (out_buf != NULL); + gst_buffer_unref (out_buf); + + + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (data.clock), &id); + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), 260 * GST_MSECOND); + tid = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (data.clock)); + g_assert (tid == id); + gst_clock_id_unref (tid); + gst_clock_id_unref (id); + + /* we should now receive a packet-lost-event for buffer 2 */ + out_event = g_async_queue_pop (data.sink_event_queue); + g_assert (out_event != NULL); + verify_lost_event (out_event, 2, 40 * GST_MSECOND, 20 * GST_MSECOND, FALSE); + + destroy_testharness (&data); +} + +GST_END_TEST; + +GST_START_TEST (test_rtx_two_missing) +{ + TestData data; + GstClockID id, tid; + GstBuffer *in_buf, *out_buf; + GstEvent *out_event; + gint jb_latency_ms = 200; + gint i; + GstStructure *rtx_stats; + const GValue *rtx_stat; + GstRTPBuffer rtp = GST_RTP_BUFFER_INIT; + + setup_testharness (&data); + g_object_set (data.jitter_buffer, "do-retransmission", TRUE, NULL); + g_object_set (data.jitter_buffer, "latency", jb_latency_ms, NULL); + g_object_set (data.jitter_buffer, "rtx-retry-period", 120, NULL); + + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), 0); + + /* push the first buffer in */ + in_buf = generate_test_buffer (0 * GST_MSECOND, TRUE, 0, 0); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), 20 * GST_MSECOND); + + /* put second buffer, the jitterbuffer should now know that the packet spacing + * is 20ms and should ask for retransmission of seqnum 2 at 60ms */ + in_buf = generate_test_buffer (20 * GST_MSECOND, TRUE, 1, 160); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + + /* push buffer 4, 2 and 3 are missing now, we should get retransmission events + * for 3 at 100ms*/ + in_buf = generate_test_buffer (80 * GST_MSECOND, TRUE, 4, 4 * 160); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + + /* wait for first retransmission request */ + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), 60 * GST_MSECOND); + do { + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (data.clock), &id); + tid = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (data.clock)); + gst_clock_id_unref (id); + gst_clock_id_unref (tid); + } while (id != tid); + + /* we should have 2 events now, one for 2 and another for 3 */ + out_event = g_async_queue_pop (data.src_event_queue); + g_assert (out_event != NULL); + verify_rtx_event (out_event, 2, 40 * GST_MSECOND, 20, 20 * GST_MSECOND); + out_event = g_async_queue_pop (data.src_event_queue); + g_assert (out_event != NULL); + verify_rtx_event (out_event, 3, 60 * GST_MSECOND, 0, 20 * GST_MSECOND); + + /* now we wait for the next timeout */ + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (data.clock), &id); + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), 100 * GST_MSECOND); + tid = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (data.clock)); + g_assert (id == tid); + gst_clock_id_unref (id); + gst_clock_id_unref (tid); + + /* we should have 2 events now, one for 2 and another for 3 */ + out_event = g_async_queue_pop (data.src_event_queue); + g_assert (out_event != NULL); + verify_rtx_event (out_event, 2, 40 * GST_MSECOND, 60, 20 * GST_MSECOND); + out_event = g_async_queue_pop (data.src_event_queue); + g_assert (out_event != NULL); + verify_rtx_event (out_event, 3, 60 * GST_MSECOND, 40, 20 * GST_MSECOND); + + /* make buffer 3 */ + in_buf = generate_test_buffer (60 * GST_MSECOND, TRUE, 3, 3 * 160); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + + /* make more buffers */ + for (i = 5; i < 15; i++) { + in_buf = generate_test_buffer (i * 20 * GST_MSECOND, TRUE, i, i * 160); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + } + + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (data.clock), &id); + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), 140 * GST_MSECOND); + tid = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (data.clock)); + g_assert (id == tid); + gst_clock_id_unref (id); + gst_clock_id_unref (tid); + + /* now we only get requests for 2 */ + out_event = g_async_queue_pop (data.src_event_queue); + g_assert (out_event != NULL); + verify_rtx_event (out_event, 2, 40 * GST_MSECOND, 100, 20 * GST_MSECOND); + + /* this is when buffer 0 deadline expires */ + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (data.clock), &id); + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), 200 * GST_MSECOND); + tid = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (data.clock)); + g_assert (id == tid); + gst_clock_id_unref (id); + gst_clock_id_unref (tid); + + for (i = 0; i < 2; i++) { + GST_DEBUG ("popping %d", i); + out_buf = g_async_queue_pop (data.buf_queue); + g_assert (out_buf != NULL); + gst_rtp_buffer_map (out_buf, GST_MAP_READ, &rtp); + g_assert_cmpint (gst_rtp_buffer_get_seq (&rtp), ==, i); + gst_rtp_buffer_unmap (&rtp); + gst_buffer_unref (out_buf); + } + + /* this is when 2 is lost */ + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (data.clock), &id); + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), 240 * GST_MSECOND); + tid = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (data.clock)); + g_assert (id == tid); + gst_clock_id_unref (id); + gst_clock_id_unref (tid); + + /* we should now receive a packet-lost-event for buffer 2 */ + out_event = g_async_queue_pop (data.sink_event_queue); + g_assert (out_event != NULL); + verify_lost_event (out_event, 2, 40 * GST_MSECOND, 20 * GST_MSECOND, FALSE); + + /* verify that buffers made it through! */ + for (i = 3; i < 15; i++) { + GST_DEBUG ("popping %d", i); + out_buf = g_async_queue_pop (data.buf_queue); + g_assert (out_buf != NULL); + gst_rtp_buffer_map (out_buf, GST_MAP_READ, &rtp); + g_assert_cmpint (gst_rtp_buffer_get_seq (&rtp), ==, i); + gst_rtp_buffer_unmap (&rtp); + gst_buffer_unref (out_buf); + } + /* should still have only seen 1 packet lost events */ + g_assert_cmpint (data.lost_event_count, ==, 1); + + g_object_get (data.jitter_buffer, "stats", &rtx_stats, NULL); + + rtx_stat = gst_structure_get_value (rtx_stats, "rtx-count"); + g_assert_cmpuint (g_value_get_uint64 (rtx_stat), ==, 5); + + rtx_stat = gst_structure_get_value (rtx_stats, "rtx-success-count"); + g_assert_cmpuint (g_value_get_uint64 (rtx_stat), ==, 1); + + rtx_stat = gst_structure_get_value (rtx_stats, "rtx-rtt"); + g_assert_cmpuint (g_value_get_uint64 (rtx_stat), ==, 0); + gst_structure_free (rtx_stats); + + destroy_testharness (&data); +} + +GST_END_TEST; + +GST_START_TEST (test_rtx_packet_delay) +{ + TestData data; + GstClockID id, tid; + GstBuffer *in_buf, *out_buf; + GstEvent *out_event; + gint jb_latency_ms = 200; + gint i; + GstRTPBuffer rtp = GST_RTP_BUFFER_INIT; + + setup_testharness (&data); + g_object_set (data.jitter_buffer, "do-retransmission", TRUE, NULL); + g_object_set (data.jitter_buffer, "latency", jb_latency_ms, NULL); + g_object_set (data.jitter_buffer, "rtx-retry-period", 120, NULL); + + /* push the first buffer in */ + in_buf = generate_test_buffer (0 * GST_MSECOND, TRUE, 0, 0); + GST_BUFFER_FLAG_SET (in_buf, GST_BUFFER_FLAG_DISCONT); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), 20 * GST_MSECOND); + + /* put second buffer, the jitterbuffer should now know that the packet spacing + * is 20ms and should ask for retransmission of seqnum 2 at 60ms */ + in_buf = generate_test_buffer (20 * GST_MSECOND, TRUE, 1, 160); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + + /* push buffer 8, 2 -> 7 are missing now. note that the rtp time is the same + * as packet 1 because it was part of a fragmented payload. This means that + * the estimate for 2 could be refined now to 20ms. also packet 2, 3 and 4 are + * exceeding the max allowed reorder distance and should request a + * retransmission right away */ + in_buf = generate_test_buffer (20 * GST_MSECOND, TRUE, 8, 8 * 160); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + + /* we should now receive retransmission requests for 2 -> 5 */ + out_event = g_async_queue_pop (data.src_event_queue); + g_assert (out_event != NULL); + verify_rtx_event (out_event, 2, 20 * GST_MSECOND, 40, 20 * GST_MSECOND); + + for (i = 3; i < 5; i++) { + GST_DEBUG ("popping %d", i); + out_event = g_async_queue_pop (data.src_event_queue); + g_assert (out_event != NULL); + verify_rtx_event (out_event, i, 20 * GST_MSECOND, 0, 20 * GST_MSECOND); + } + g_assert_cmpint (data.rtx_event_count, ==, 3); + + /* push 9, this should immediately request retransmission of 5 */ + in_buf = generate_test_buffer (20 * GST_MSECOND, TRUE, 9, 9 * 160); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + + /* we should now receive retransmission requests for 5 */ + out_event = g_async_queue_pop (data.src_event_queue); + g_assert (out_event != NULL); + verify_rtx_event (out_event, 5, 20 * GST_MSECOND, 0, 20 * GST_MSECOND); + + /* wait for timeout for rtx 6 -> 7 */ + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (data.clock), &id); + tid = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (data.clock)); + g_assert (id == tid); + gst_clock_id_unref (id); + gst_clock_id_unref (tid); + + for (i = 6; i < 8; i++) { + GST_DEBUG ("popping %d", i); + out_event = g_async_queue_pop (data.src_event_queue); + g_assert (out_event != NULL); + verify_rtx_event (out_event, i, 20 * GST_MSECOND, 0, 20 * GST_MSECOND); + } + + /* churn through sync_times until the new buffer gets pushed out */ + while (g_async_queue_length (data.buf_queue) < 1) { + if (gst_test_clock_peek_next_pending_id (GST_TEST_CLOCK (data.clock), &id)) { + GstClockTime t = gst_clock_id_get_time (id); + if (t >= 240 * GST_MSECOND) { + gst_clock_id_unref (id); + break; + } + if (t > gst_clock_get_time (data.clock)) { + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), t); + } + tid = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (data.clock)); + gst_clock_id_unref (id); + gst_clock_id_unref (tid); + } + } + + /* verify that buffer 0 and 1 made it through! */ + for (i = 0; i < 2; i++) { + out_buf = g_async_queue_pop (data.buf_queue); + g_assert (out_buf != NULL); + if (i == 0) + g_assert (GST_BUFFER_FLAG_IS_SET (out_buf, GST_BUFFER_FLAG_DISCONT)); + gst_rtp_buffer_map (out_buf, GST_MAP_READ, &rtp); + g_assert_cmpint (gst_rtp_buffer_get_seq (&rtp), ==, i); + gst_rtp_buffer_unmap (&rtp); + gst_buffer_unref (out_buf); + } + + /* churn through sync_times until the next buffer gets pushed out */ + while (g_async_queue_length (data.buf_queue) < 1) { + if (gst_test_clock_peek_next_pending_id (GST_TEST_CLOCK (data.clock), &id)) { + GstClockTime t = gst_clock_id_get_time (id); + if (t >= 240 * GST_MSECOND) { + gst_clock_id_unref (id); + break; + } + if (t > gst_clock_get_time (data.clock)) { + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), t); + } + tid = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (data.clock)); + gst_clock_id_unref (id); + gst_clock_id_unref (tid); + } + } + + for (i = 2; i < 8; i++) { + GST_DEBUG ("popping lost event %d", i); + out_event = g_async_queue_pop (data.sink_event_queue); + g_assert (out_event != NULL); + verify_lost_event (out_event, i, 20 * GST_MSECOND, 0, FALSE); + } + + /* verify that buffer 8 made it through! */ + for (i = 8; i < 10; i++) { + GST_DEBUG ("popping buffer %d", i); + out_buf = g_async_queue_pop (data.buf_queue); + g_assert (out_buf != NULL); + if (i == 8) + g_assert (GST_BUFFER_FLAG_IS_SET (out_buf, GST_BUFFER_FLAG_DISCONT)); + gst_rtp_buffer_map (out_buf, GST_MAP_READ, &rtp); + g_assert_cmpint (gst_rtp_buffer_get_seq (&rtp), ==, i); + gst_rtp_buffer_unmap (&rtp); + gst_buffer_unref (out_buf); + } + + GST_DEBUG ("waiting for 240ms"); + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (data.clock), &id); + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), 240 * GST_MSECOND); + tid = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (data.clock)); + g_assert (id == tid); + gst_clock_id_unref (id); + gst_clock_id_unref (tid); + + GST_DEBUG ("popping lost event 10"); + out_event = g_async_queue_pop (data.sink_event_queue); + g_assert (out_event != NULL); + verify_lost_event (out_event, 10, 40 * GST_MSECOND, 20 * GST_MSECOND, FALSE); + + /* should have seen 6 packet lost events */ + g_assert_cmpint (data.lost_event_count, ==, 7); + g_assert_cmpint (data.rtx_event_count, ==, 26); + + destroy_testharness (&data); +} + +GST_END_TEST; + +GST_START_TEST (test_gap_exceeds_latency) +{ + TestData data; + GstBuffer *in_buf, *out_buf; + GstClockID id, tid; + GstEvent *out_event; + guint32 timestamp_ms = 0; + guint32 last_ts = 0; + gint jb_latency_ms = 200; + guint32 rtp_ts = 0; + guint32 last_rtp = 0; + const GstStructure *s = NULL; + guint32 seqnum = 0; + gint i; + + setup_testharness (&data); + g_object_set (data.jitter_buffer, "do-retransmission", TRUE, NULL); + g_object_set (data.jitter_buffer, "latency", jb_latency_ms, NULL); + g_object_set (data.jitter_buffer, "rtx-retry-period", 120, NULL); + + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), 0); + in_buf = generate_test_buffer (timestamp_ms * GST_MSECOND, TRUE, 0, rtp_ts); + GST_BUFFER_FLAG_SET (in_buf, GST_BUFFER_FLAG_DISCONT); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + + timestamp_ms += 20; + rtp_ts += 160; + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), + timestamp_ms * GST_MSECOND); + + in_buf = generate_test_buffer (timestamp_ms * GST_MSECOND, TRUE, 1, rtp_ts); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + last_rtp = rtp_ts; + last_ts = timestamp_ms; + + /* Allow seqnum 2 to be declared lost */ + do { + out_event = g_async_queue_try_pop (data.sink_event_queue); + if (!out_event) { + if (gst_test_clock_peek_next_pending_id (GST_TEST_CLOCK (data.clock), + &id)) { + + GstClockTime t = gst_clock_id_get_time (id); + if (t > gst_clock_get_time (data.clock)) { + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), t); + } + tid = + gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (data.clock)); + gst_clock_id_unref (id); + gst_clock_id_unref (tid); + } + } + } while (!out_event); + + out_buf = g_async_queue_pop (data.buf_queue); + gst_buffer_unref (out_buf); + + out_buf = g_async_queue_pop (data.buf_queue); + gst_buffer_unref (out_buf); + + timestamp_ms += (20 * 15); + s = gst_event_get_structure (out_event); + g_assert (gst_structure_get_uint (s, "seqnum", &seqnum)); + g_assert_cmpint (seqnum, ==, 2); + gst_event_unref (out_event); + + /* Now data comes in again, a "bulk" lost packet is created for 3 -> 6 */ + rtp_ts += (160 * 15); + in_buf = generate_test_buffer (timestamp_ms * GST_MSECOND, TRUE, 16, rtp_ts); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + + last_ts += 60; + last_rtp += 480; + in_buf = generate_test_buffer (last_ts * GST_MSECOND, TRUE, 8, last_rtp); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + + last_ts += 20; + last_rtp += 160; + in_buf = generate_test_buffer (last_ts * GST_MSECOND, TRUE, 9, last_rtp); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + + last_ts += 20; + last_rtp += 160; + in_buf = generate_test_buffer (last_ts * GST_MSECOND, TRUE, 10, last_rtp); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + + last_ts += 20; + last_rtp += 160; + in_buf = generate_test_buffer (last_ts * GST_MSECOND, TRUE, 11, last_rtp); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + + last_ts += 20; + last_rtp += 160; + in_buf = generate_test_buffer (last_ts * GST_MSECOND, TRUE, 12, last_rtp); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + + last_ts += 20; + last_rtp += 160; + in_buf = generate_test_buffer (last_ts * GST_MSECOND, TRUE, 13, last_rtp); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + + last_ts += 20; + last_rtp += 160; + in_buf = generate_test_buffer (last_ts * GST_MSECOND, TRUE, 14, last_rtp); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + + last_ts += 20; + last_rtp += 160; + in_buf = generate_test_buffer (last_ts * GST_MSECOND, TRUE, 15, last_rtp); + g_assert_cmpint (gst_pad_push (data.test_src_pad, in_buf), ==, GST_FLOW_OK); + + /* Wait for data to be pushed. */ + while (g_async_queue_length (data.buf_queue) < 1) { + if (gst_test_clock_peek_next_pending_id (GST_TEST_CLOCK (data.clock), &id)) { + GstClockTime t = gst_clock_id_get_time (id); + if (t > gst_clock_get_time (data.clock)) { + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), t); + } + tid = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (data.clock)); + gst_clock_id_unref (id); + gst_clock_id_unref (tid); + } + } + + out_event = g_async_queue_pop (data.sink_event_queue); + s = gst_event_get_structure (out_event); + g_assert (gst_structure_get_uint (s, "seqnum", &seqnum)); + g_assert_cmpint (seqnum, ==, 3); + gst_event_unref (out_event); + + out_event = g_async_queue_pop (data.sink_event_queue); + s = gst_event_get_structure (out_event); + g_assert (gst_structure_get_uint (s, "seqnum", &seqnum)); + g_assert_cmpint (seqnum, ==, 7); + gst_event_unref (out_event); + + /* 8 */ + for (i = 8; i <= 16; i++) { + out_buf = g_async_queue_pop (data.buf_queue); + GST_DEBUG ("pop %d", i); + gst_buffer_unref (out_buf); + } + + do { + out_event = g_async_queue_try_pop (data.sink_event_queue); + if (!out_event) { + if (gst_test_clock_peek_next_pending_id (GST_TEST_CLOCK (data.clock), + &id)) { + + GstClockTime t = gst_clock_id_get_time (id); + if (t > gst_clock_get_time (data.clock)) { + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), t); + } + tid = + gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (data.clock)); + gst_clock_id_unref (id); + gst_clock_id_unref (tid); + } + } + } while (!out_event); + + /* and lost of 17 */ + s = gst_event_get_structure (out_event); + g_assert (gst_structure_get_uint (s, "seqnum", &seqnum)); + g_assert_cmpint (seqnum, ==, 17); + gst_event_unref (out_event); + + destroy_testharness (&data); +} + +GST_END_TEST; + + +static Suite * +rtpjitterbuffer_suite (void) +{ + Suite *s = suite_create ("rtpjitterbuffer"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_push_forward_seq); + tcase_add_test (tc_chain, test_push_backward_seq); + tcase_add_test (tc_chain, test_push_unordered); + tcase_add_test (tc_chain, test_basetime); + tcase_add_test (tc_chain, test_clear_pt_map); + tcase_add_test (tc_chain, test_only_one_lost_event_on_large_gaps); + tcase_add_test (tc_chain, test_two_lost_one_arrives_in_time); + tcase_add_test (tc_chain, test_late_packets_still_makes_lost_events); + tcase_add_test (tc_chain, test_all_packets_are_timestamped_zero); + tcase_add_test (tc_chain, test_rtx_expected_next); + tcase_add_test (tc_chain, test_rtx_two_missing); + tcase_add_test (tc_chain, test_rtx_packet_delay); + tcase_add_test (tc_chain, test_gap_exceeds_latency); + + return s; +} + +GST_CHECK_MAIN (rtpjitterbuffer); diff --git a/tests/check/elements/rtpmux.c b/tests/check/elements/rtpmux.c new file mode 100755 index 0000000..11ba979 --- /dev/null +++ b/tests/check/elements/rtpmux.c @@ -0,0 +1,320 @@ +/* GStreamer + * + * unit test for rtpmux elements + * + * Copyright 2009 Collabora Ltd. + * @author: Olivier Crete <olivier.crete@collabora.co.uk> + * Copyright 2009 Nokia Corp. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <gst/check/gstcheck.h> +#include <gst/rtp/gstrtpbuffer.h> +#include <gst/gst.h> + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-rtp")); + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-rtp")); + +typedef void (*check_cb) (GstPad * pad, int i); + +static gboolean +query_func (GstPad * pad, GstObject * noparent, GstQuery * query) +{ + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_CAPS: + { + GstCaps **caps = g_object_get_data (G_OBJECT (pad), "caps"); + + fail_unless (caps != NULL && *caps != NULL); + gst_query_set_caps_result (query, *caps); + break; + } + case GST_QUERY_ACCEPT_CAPS: + gst_query_set_accept_caps_result (query, TRUE); + break; + default: + break; + } + + return TRUE; +} + +static gboolean +event_func (GstPad * pad, GstObject * noparent, GstEvent * event) +{ + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_CAPS: + { + GstCaps *caps; + GstCaps **caps2 = g_object_get_data (G_OBJECT (pad), "caps"); + + gst_event_parse_caps (event, &caps); + fail_unless (caps2 != NULL && *caps2 != NULL); + fail_unless (gst_caps_is_fixed (caps)); + fail_unless (gst_caps_is_fixed (*caps2)); + fail_unless (gst_caps_is_equal_fixed (caps, *caps2)); + break; + } + default: + break; + } + + gst_event_unref (event); + + return TRUE; +} + +static void +test_basic (const gchar * elem_name, const gchar * sink2, int count, + check_cb cb) +{ + GstElement *rtpmux = NULL; + GstPad *reqpad1 = NULL; + GstPad *reqpad2 = NULL; + GstPad *src1 = NULL; + GstPad *src2 = NULL; + GstPad *sink = NULL; + GstBuffer *inbuf = NULL; + GstCaps *src1caps = NULL; + GstCaps *src2caps = NULL; + GstCaps *sinkcaps = NULL; + GstCaps *caps; + GstSegment segment; + int i; + + rtpmux = gst_check_setup_element (elem_name); + + reqpad1 = gst_element_get_request_pad (rtpmux, "sink_1"); + fail_unless (reqpad1 != NULL); + reqpad2 = gst_element_get_request_pad (rtpmux, sink2); + fail_unless (reqpad2 != NULL); + sink = gst_check_setup_sink_pad_by_name (rtpmux, &sinktemplate, "src"); + + src1 = gst_pad_new_from_static_template (&srctemplate, "src"); + src2 = gst_pad_new_from_static_template (&srctemplate, "src"); + fail_unless (gst_pad_link (src1, reqpad1) == GST_PAD_LINK_OK); + fail_unless (gst_pad_link (src2, reqpad2) == GST_PAD_LINK_OK); + gst_pad_set_query_function (src1, query_func); + gst_pad_set_query_function (src2, query_func); + gst_pad_set_query_function (sink, query_func); + gst_pad_set_event_function (sink, event_func); + g_object_set_data (G_OBJECT (src1), "caps", &src1caps); + g_object_set_data (G_OBJECT (src2), "caps", &src2caps); + g_object_set_data (G_OBJECT (sink), "caps", &sinkcaps); + + src1caps = gst_caps_new_simple ("application/x-rtp", + "clock-rate", G_TYPE_INT, 1, "ssrc", G_TYPE_UINT, 11, NULL); + src2caps = gst_caps_new_simple ("application/x-rtp", + "clock-rate", G_TYPE_INT, 2, "ssrc", G_TYPE_UINT, 12, NULL); + sinkcaps = gst_caps_new_simple ("application/x-rtp", + "clock-rate", G_TYPE_INT, 3, "ssrc", G_TYPE_UINT, 13, NULL); + + caps = gst_pad_peer_query_caps (src1, NULL); + fail_unless (gst_caps_is_empty (caps)); + gst_caps_unref (caps); + + gst_caps_set_simple (src2caps, "clock-rate", G_TYPE_INT, 3, NULL); + caps = gst_pad_peer_query_caps (src1, NULL); + fail_unless (gst_caps_is_equal (caps, sinkcaps)); + gst_caps_unref (caps); + + g_object_set (rtpmux, "seqnum-offset", 100, "timestamp-offset", 1000, + "ssrc", 55, NULL); + + fail_unless (gst_element_set_state (rtpmux, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS); + gst_pad_set_active (sink, TRUE); + gst_pad_set_active (src1, TRUE); + gst_pad_set_active (src2, TRUE); + + fail_unless (gst_pad_push_event (src1, + gst_event_new_stream_start ("stream1"))); + fail_unless (gst_pad_push_event (src2, + gst_event_new_stream_start ("stream2"))); + + gst_caps_set_simple (sinkcaps, + "payload", G_TYPE_INT, 98, "seqnum-offset", G_TYPE_UINT, 100, + "timestamp-offset", G_TYPE_UINT, 1000, "ssrc", G_TYPE_UINT, 66, NULL); + caps = gst_caps_new_simple ("application/x-rtp", + "payload", G_TYPE_INT, 98, "clock-rate", G_TYPE_INT, 3, + "seqnum-offset", G_TYPE_UINT, 56, "timestamp-offset", G_TYPE_UINT, 57, + "ssrc", G_TYPE_UINT, 66, NULL); + fail_unless (gst_pad_set_caps (src1, caps)); + gst_caps_unref (caps); + + caps = gst_pad_peer_query_caps (sink, NULL); + fail_if (gst_caps_is_empty (caps)); + + gst_segment_init (&segment, GST_FORMAT_TIME); + segment.start = 100000; + fail_unless (gst_pad_push_event (src1, gst_event_new_segment (&segment))); + segment.start = 0; + fail_unless (gst_pad_push_event (src2, gst_event_new_segment (&segment))); + + + for (i = 0; i < count; i++) { + GstRTPBuffer rtpbuffer = GST_RTP_BUFFER_INIT; + + inbuf = gst_rtp_buffer_new_allocate (10, 0, 0); + GST_BUFFER_PTS (inbuf) = i * 1000 + 100000; + GST_BUFFER_DURATION (inbuf) = 1000; + + gst_rtp_buffer_map (inbuf, GST_MAP_WRITE, &rtpbuffer); + + gst_rtp_buffer_set_version (&rtpbuffer, 2); + gst_rtp_buffer_set_payload_type (&rtpbuffer, 98); + gst_rtp_buffer_set_ssrc (&rtpbuffer, 44); + gst_rtp_buffer_set_timestamp (&rtpbuffer, 200 + i); + gst_rtp_buffer_set_seq (&rtpbuffer, 2000 + i); + gst_rtp_buffer_unmap (&rtpbuffer); + fail_unless (gst_pad_push (src1, inbuf) == GST_FLOW_OK); + + if (buffers) + fail_unless (GST_BUFFER_PTS (buffers->data) == i * 1000, "%lld", + GST_BUFFER_PTS (buffers->data)); + + cb (src2, i); + + g_list_foreach (buffers, (GFunc) gst_buffer_unref, NULL); + g_list_free (buffers); + buffers = NULL; + } + + + gst_pad_set_active (sink, FALSE); + gst_pad_set_active (src1, FALSE); + gst_pad_set_active (src2, FALSE); + fail_unless (gst_element_set_state (rtpmux, + GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS); + gst_check_teardown_pad_by_name (rtpmux, "src"); + gst_object_unref (reqpad1); + gst_object_unref (reqpad2); + gst_check_teardown_pad_by_name (rtpmux, "sink_1"); + gst_check_teardown_pad_by_name (rtpmux, sink2); + gst_element_release_request_pad (rtpmux, reqpad1); + gst_element_release_request_pad (rtpmux, reqpad2); + + gst_caps_unref (caps); + gst_caps_replace (&src1caps, NULL); + gst_caps_replace (&src2caps, NULL); + gst_caps_replace (&sinkcaps, NULL); + + gst_check_teardown_element (rtpmux); +} + +static void +basic_check_cb (GstPad * pad, int i) +{ + GstRTPBuffer rtpbuffer = GST_RTP_BUFFER_INIT; + fail_unless (buffers && g_list_length (buffers) == 1); + + gst_rtp_buffer_map (buffers->data, GST_MAP_READ, &rtpbuffer); + fail_unless (gst_rtp_buffer_get_ssrc (&rtpbuffer) == 66); + fail_unless (gst_rtp_buffer_get_timestamp (&rtpbuffer) == + 200 - 57 + 1000 + i); + fail_unless (gst_rtp_buffer_get_seq (&rtpbuffer) == 100 + 1 + i); + gst_rtp_buffer_unmap (&rtpbuffer); +} + + +GST_START_TEST (test_rtpmux_basic) +{ + test_basic ("rtpmux", "sink_2", 10, basic_check_cb); +} + +GST_END_TEST; + +GST_START_TEST (test_rtpdtmfmux_basic) +{ + test_basic ("rtpdtmfmux", "sink_2", 10, basic_check_cb); +} + +GST_END_TEST; + +static void +lock_check_cb (GstPad * pad, int i) +{ + GstBuffer *inbuf; + + if (i % 2) { + fail_unless (buffers == NULL); + } else { + GstRTPBuffer rtpbuffer = GST_RTP_BUFFER_INIT; + + fail_unless (buffers && g_list_length (buffers) == 1); + gst_rtp_buffer_map (buffers->data, GST_MAP_READ, &rtpbuffer); + fail_unless (gst_rtp_buffer_get_ssrc (&rtpbuffer) == 66); + fail_unless (gst_rtp_buffer_get_timestamp (&rtpbuffer) == + 200 - 57 + 1000 + i); + fail_unless (gst_rtp_buffer_get_seq (&rtpbuffer) == 100 + 1 + i); + gst_rtp_buffer_unmap (&rtpbuffer); + + inbuf = gst_rtp_buffer_new_allocate (10, 0, 0); + GST_BUFFER_PTS (inbuf) = i * 1000 + 500; + GST_BUFFER_DURATION (inbuf) = 1000; + gst_rtp_buffer_map (inbuf, GST_MAP_WRITE, &rtpbuffer); + gst_rtp_buffer_set_version (&rtpbuffer, 2); + gst_rtp_buffer_set_payload_type (&rtpbuffer, 98); + gst_rtp_buffer_set_ssrc (&rtpbuffer, 44); + gst_rtp_buffer_set_timestamp (&rtpbuffer, 200 + i); + gst_rtp_buffer_set_seq (&rtpbuffer, 2000 + i); + gst_rtp_buffer_unmap (&rtpbuffer); + fail_unless (gst_pad_push (pad, inbuf) == GST_FLOW_OK); + + + g_list_foreach (buffers, (GFunc) gst_buffer_unref, NULL); + g_list_free (buffers); + buffers = NULL; + } +} + +GST_START_TEST (test_rtpdtmfmux_lock) +{ + test_basic ("rtpdtmfmux", "priority_sink_2", 10, lock_check_cb); +} + +GST_END_TEST; + +static Suite * +rtpmux_suite (void) +{ + Suite *s = suite_create ("rtpmux"); + TCase *tc_chain; + + tc_chain = tcase_create ("rtpmux_basic"); + tcase_add_test (tc_chain, test_rtpmux_basic); + suite_add_tcase (s, tc_chain); + + tc_chain = tcase_create ("rtpdtmfmux_basic"); + tcase_add_test (tc_chain, test_rtpdtmfmux_basic); + suite_add_tcase (s, tc_chain); + + tc_chain = tcase_create ("rtpdtmfmux_lock"); + tcase_add_test (tc_chain, test_rtpdtmfmux_lock); + suite_add_tcase (s, tc_chain); + + return s; +} + +GST_CHECK_MAIN (rtpmux) diff --git a/tests/check/elements/rtprtx.c b/tests/check/elements/rtprtx.c new file mode 100755 index 0000000..706f291 --- /dev/null +++ b/tests/check/elements/rtprtx.c @@ -0,0 +1,1546 @@ +/* GStreamer + * + * Copyright (C) 2013 Collabora Ltd. + * @author Julien Isorce <julien.isorce@collabora.co.uk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <gst/check/gstcheck.h> +#include <gst/check/gstconsistencychecker.h> + +#include <gst/rtp/gstrtpbuffer.h> + +/* 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 *srcpad, *sinkpad; +/* we also have a list of src buffers */ +static GList *inbuffers = NULL; + +#define RTP_CAPS_STRING \ + "application/x-rtp, " \ + "media = (string)audio, " \ + "payload = (int) 0, " \ + "clock-rate = (int) 8000, " \ + "ssrc = (uint) 42, " \ + "encoding-name = (string)PCMU" + +#define RTP_FRAME_SIZE 20 + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-rtp") + ); +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-rtp") + ); + +static void +setup_rtprtx (GstElement * rtprtxsend, GstElement * rtprtxreceive, + gint num_buffers) +{ + GstBuffer *buffer; + GstPad *sendsrcpad; + GstPad *receivesinkpad; + gboolean ret = FALSE; + + /* a 20 sample audio block (2,5 ms) generated with + * gst-launch audiotestsrc wave=silence blocksize=40 num-buffers=3 ! + * "audio/x-raw,channels=1,rate=8000" ! mulawenc ! rtppcmupay ! + * fakesink dump=1 + */ + guint8 in[] = { /* first 4 bytes are rtp-header, next 4 bytes are timestamp */ + 0x80, 0x80, 0x1c, 0x24, 0x46, 0xcd, 0xb7, 0x11, 0x3c, 0x3a, 0x7c, 0x5b, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; + GstClockTime ts = G_GUINT64_CONSTANT (0); + GstClockTime tso = gst_util_uint64_scale (RTP_FRAME_SIZE, GST_SECOND, 8000); + gint i; + + srcpad = gst_check_setup_src_pad (rtprtxsend, &srctemplate); + sendsrcpad = gst_element_get_static_pad (rtprtxsend, "src"); + ret = gst_pad_set_active (srcpad, TRUE); + fail_if (ret == FALSE); + + sinkpad = gst_check_setup_sink_pad (rtprtxreceive, &sinktemplate); + receivesinkpad = gst_element_get_static_pad (rtprtxreceive, "sink"); + ret = gst_pad_set_active (sinkpad, TRUE); + fail_if (ret == FALSE); + + fail_if (gst_pad_link (sendsrcpad, receivesinkpad) != GST_PAD_LINK_OK); + + ret = gst_pad_set_active (sendsrcpad, TRUE); + fail_if (ret == FALSE); + ret = gst_pad_set_active (receivesinkpad, TRUE); + fail_if (ret == FALSE); + + gst_object_unref (sendsrcpad); + gst_object_unref (receivesinkpad); + + for (i = 0; i < num_buffers; i++) { + buffer = gst_buffer_new_and_alloc (sizeof (in)); + gst_buffer_fill (buffer, 0, in, sizeof (in)); + GST_BUFFER_DTS (buffer) = ts; + GST_BUFFER_PTS (buffer) = ts; + GST_BUFFER_DURATION (buffer) = tso; + GST_DEBUG ("created buffer: %p", buffer); + + /*if (!i) + GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT); */ + + inbuffers = g_list_append (inbuffers, buffer); + + /* hackish way to update the rtp header */ + in[1] = 0x00; + in[3]++; /* seqnumber */ + in[7] += RTP_FRAME_SIZE; /* inc. timestamp with framesize */ + ts += tso; + } +} + +static GstStateChangeReturn +start_rtprtx (GstElement * element) +{ + GstStateChangeReturn ret; + + ret = gst_element_set_state (element, GST_STATE_PLAYING); + ck_assert_int_ne (ret, GST_STATE_CHANGE_FAILURE); + + ret = gst_element_get_state (element, NULL, NULL, GST_CLOCK_TIME_NONE); + ck_assert_int_ne (ret, GST_STATE_CHANGE_FAILURE); + + return ret; +} + +static void +cleanup_rtprtx (GstElement * rtprtxsend, GstElement * rtprtxreceive) +{ + GST_DEBUG ("cleanup_rtprtx"); + + g_list_free (inbuffers); + inbuffers = NULL; + + gst_pad_set_active (srcpad, FALSE); + gst_check_teardown_src_pad (rtprtxsend); + gst_check_teardown_element (rtprtxsend); + + gst_pad_set_active (sinkpad, FALSE); + gst_check_teardown_sink_pad (rtprtxreceive); + gst_check_teardown_element (rtprtxreceive); +} + +static void +check_rtprtx_results (GstElement * rtprtxsend, GstElement * rtprtxreceive, + gint num_buffers) +{ + guint nbrtxrequests = 0; + guint nbrtxpackets = 0; + + g_object_get (G_OBJECT (rtprtxsend), "num-rtx-requests", &nbrtxrequests, + NULL); + fail_unless_equals_int (nbrtxrequests, 3); + + g_object_get (G_OBJECT (rtprtxsend), "num-rtx-packets", &nbrtxpackets, NULL); + fail_unless_equals_int (nbrtxpackets, 3); + + g_object_get (G_OBJECT (rtprtxreceive), "num-rtx-requests", &nbrtxrequests, + NULL); + fail_unless_equals_int (nbrtxrequests, 3); + + g_object_get (G_OBJECT (rtprtxreceive), "num-rtx-packets", &nbrtxpackets, + NULL); + fail_unless_equals_int (nbrtxpackets, 3); + + g_object_get (G_OBJECT (rtprtxreceive), "num-rtx-assoc-packets", + &nbrtxpackets, NULL); + fail_unless_equals_int (nbrtxpackets, 3); +} + + +GST_START_TEST (test_push_forward_seq) +{ + GstElement *rtprtxsend; + GstElement *rtprtxreceive; + const guint num_buffers = 4; + GList *node; + gint i = 0; + GstCaps *caps = NULL; + GstStructure *pt_map; + + rtprtxsend = gst_check_setup_element ("rtprtxsend"); + rtprtxreceive = gst_check_setup_element ("rtprtxreceive"); + setup_rtprtx (rtprtxsend, rtprtxreceive, num_buffers); + GST_DEBUG ("setup_rtprtx"); + + fail_unless (start_rtprtx (rtprtxsend) + == GST_STATE_CHANGE_SUCCESS, "could not set to playing"); + + fail_unless (start_rtprtx (rtprtxreceive) + == GST_STATE_CHANGE_SUCCESS, "could not set to playing"); + + caps = gst_caps_from_string (RTP_CAPS_STRING); + gst_check_setup_events (srcpad, rtprtxsend, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + + pt_map = gst_structure_new ("application/x-rtp-pt-map", + "0", G_TYPE_UINT, 97, NULL); + g_object_set (rtprtxsend, "payload-type-map", pt_map, NULL); + g_object_set (rtprtxreceive, "payload-type-map", pt_map, NULL); + gst_structure_free (pt_map); + + /* push buffers: 0,1,2, */ + for (node = inbuffers; node; node = g_list_next (node)) { + GstEvent *event = NULL; + GstRTPBuffer rtp = GST_RTP_BUFFER_INIT; + GstBuffer *buffer = (GstBuffer *) node->data; + GList *last_out_buffer; + guint64 end_time; + gboolean res; + + gst_buffer_ref (buffer); + fail_unless_equals_int (gst_pad_push (srcpad, buffer), GST_FLOW_OK); + + if (i < 3) { + gst_rtp_buffer_map (buffer, GST_MAP_READ, &rtp); + event = gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, + gst_structure_new ("GstRTPRetransmissionRequest", + "seqnum", G_TYPE_UINT, (guint) gst_rtp_buffer_get_seq (&rtp), + "ssrc", G_TYPE_UINT, (guint) gst_rtp_buffer_get_ssrc (&rtp), + "payload-type", G_TYPE_UINT, + (guint) gst_rtp_buffer_get_payload_type (&rtp), NULL)); + gst_rtp_buffer_unmap (&rtp); + + /* synchronize with the chain() function of the "sinkpad" + * to make sure that rtxsend has pushed the rtx buffer out + * before continuing */ + last_out_buffer = g_list_last (buffers); + g_mutex_lock (&check_mutex); + fail_unless (gst_pad_push_event (sinkpad, event)); + end_time = g_get_monotonic_time () + G_TIME_SPAN_SECOND; + do + res = g_cond_wait_until (&check_cond, &check_mutex, end_time); + while (res == TRUE && last_out_buffer == g_list_last (buffers)); + g_mutex_unlock (&check_mutex); + } + gst_buffer_unref (buffer); + ++i; + } + + /* check the buffer list */ + check_rtprtx_results (rtprtxsend, rtprtxreceive, num_buffers); + + /* cleanup */ + cleanup_rtprtx (rtprtxsend, rtprtxreceive); +} + +GST_END_TEST; + +static void +message_received (GstBus * bus, GstMessage * message, gboolean * eos) +{ + GST_INFO ("bus message from \"%" GST_PTR_FORMAT "\": %" GST_PTR_FORMAT, + GST_MESSAGE_SRC (message), message); + + switch (message->type) { + case GST_MESSAGE_EOS: + *eos = TRUE; + break; + case GST_MESSAGE_WARNING:{ + GError *gerror; + gchar *debug; + + gst_message_parse_warning (message, &gerror, &debug); + gst_object_default_error (GST_MESSAGE_SRC (message), gerror, debug); + g_error_free (gerror); + g_free (debug); + break; + } + case GST_MESSAGE_ERROR:{ + GError *gerror; + gchar *debug; + + gst_message_parse_error (message, &gerror, &debug); + gst_object_default_error (GST_MESSAGE_SRC (message), gerror, debug); + fail ("Error: %s / %s", gerror->message, debug); + g_error_free (gerror); + g_free (debug); + break; + } + default: + break; + } +} + +typedef struct +{ + guint count; + guint nb_packets; + guint drop_every_n_packets; +} RTXSendData; + +typedef struct +{ + guint nb_packets; + guint seqnum_offset; + guint seqnum_prev; +} RTXReceiveData; + +static GstPadProbeReturn +rtprtxsend_srcpad_probe (GstPad * pad, GstPadProbeInfo * info, + gpointer user_data) +{ + GstPadProbeReturn ret = GST_PAD_PROBE_OK; + + if (info->type == (GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_PUSH)) { + GstBuffer *buffer = GST_BUFFER (info->data); + RTXSendData *rtxdata = (RTXSendData *) user_data; + GstRTPBuffer rtp = GST_RTP_BUFFER_INIT; + guint payload_type = 0; + + gst_rtp_buffer_map (buffer, GST_MAP_READ, &rtp); + payload_type = gst_rtp_buffer_get_payload_type (&rtp); + + /* main stream packets */ + if (payload_type == 96) { + /* count packets of the main stream */ + ++rtxdata->nb_packets; + /* drop some packets */ + if (rtxdata->count < rtxdata->drop_every_n_packets) { + ++rtxdata->count; + } else { + /* drop a packet every 'rtxdata->count' packets */ + rtxdata->count = 1; + ret = GST_PAD_PROBE_DROP; + } + } else { + /* retransmission packets */ + } + + gst_rtp_buffer_unmap (&rtp); + } + + return ret; +} + +static GstPadProbeReturn +rtprtxreceive_srcpad_probe (GstPad * pad, GstPadProbeInfo * info, + gpointer user_data) +{ + if (info->type == (GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_PUSH)) { + GstBuffer *buffer = GST_BUFFER (info->data); + RTXReceiveData *rtxdata = (RTXReceiveData *) user_data; + GstRTPBuffer rtp = GST_RTP_BUFFER_INIT; + guint seqnum = 0; + guint i = 0; + + gst_rtp_buffer_map (buffer, GST_MAP_READ, &rtp); + seqnum = gst_rtp_buffer_get_seq (&rtp); + + /* check if there is a dropped packet */ + if (seqnum > rtxdata->seqnum_prev + rtxdata->seqnum_offset) { + GstPad *peerpad = gst_pad_get_peer (pad); + + /* ask retransmission of missing packet */ + for (i = rtxdata->seqnum_prev + rtxdata->seqnum_offset; i < seqnum; + i += rtxdata->seqnum_offset) { + GstEvent *event = gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, + gst_structure_new ("GstRTPRetransmissionRequest", + "seqnum", G_TYPE_UINT, i, + "ssrc", G_TYPE_UINT, gst_rtp_buffer_get_ssrc (&rtp), + "payload-type", G_TYPE_UINT, + gst_rtp_buffer_get_payload_type (&rtp), + NULL)); + gst_pad_push_event (peerpad, event); + } + gst_object_unref (peerpad); + + rtxdata->seqnum_prev = seqnum; + } else if (seqnum == rtxdata->seqnum_prev + rtxdata->seqnum_offset) { + /* also update previous seqnum in this case */ + rtxdata->seqnum_prev = seqnum; + } + + gst_rtp_buffer_unmap (&rtp); + + ++rtxdata->nb_packets; + } + + return GST_PAD_PROBE_OK; +} + +static void +start_test_drop_and_check_results (GstElement * bin, GstElement * rtppayloader, + GstElement * rtprtxsend, GstElement * rtprtxreceive, + RTXSendData * send_rtxdata, RTXReceiveData * receive_rtxdata, + guint drop_every_n_packets, gboolean * eos) +{ + GstStateChangeReturn state_res = GST_STATE_CHANGE_FAILURE; + guint nbrtxrequests = 0; + guint nbrtxpackets = 0; + guint nb_expected_requests = 0; + GstStructure *pt_map; + + GST_INFO ("starting test"); + + pt_map = gst_structure_new ("application/x-rtp-pt-map", + "96", G_TYPE_UINT, 99, NULL); + g_object_set (rtppayloader, "pt", 96, NULL); + g_object_set (rtppayloader, "seqnum-offset", 1, NULL); + g_object_set (rtprtxsend, "payload-type-map", pt_map, NULL); + g_object_set (rtprtxreceive, "payload-type-map", pt_map, NULL); + gst_structure_free (pt_map); + + send_rtxdata->count = 1; + send_rtxdata->nb_packets = 0; + send_rtxdata->drop_every_n_packets = drop_every_n_packets; + + receive_rtxdata->nb_packets = 0; + receive_rtxdata->seqnum_offset = 0; + receive_rtxdata->seqnum_prev = 0; + + *eos = FALSE; + + /* retrieve offset before going to paused */ + g_object_get (G_OBJECT (rtppayloader), "seqnum-offset", + &receive_rtxdata->seqnum_offset, NULL); + + /* prepare playing */ + state_res = gst_element_set_state (bin, GST_STATE_PAUSED); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + /* wait for completion */ + state_res = gst_element_get_state (bin, NULL, NULL, GST_CLOCK_TIME_NONE); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + /* retrieve seqnum_prev here to make sure it has been reseted */ + g_object_get (G_OBJECT (rtppayloader), "seqnum", + &receive_rtxdata->seqnum_prev, NULL); + + /* run pipeline */ + state_res = gst_element_set_state (bin, GST_STATE_PLAYING); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + GST_INFO ("running main loop"); + while (!*eos) + g_main_context_iteration (NULL, TRUE); + + /* check results */ + + if (send_rtxdata->nb_packets % drop_every_n_packets == 0) { + /* special case because the last buffer will be dropped + * so the receiver cannot know if it has been dropped (no next packet) + */ + nb_expected_requests = send_rtxdata->nb_packets / drop_every_n_packets - 1; + fail_unless_equals_int (send_rtxdata->nb_packets, + receive_rtxdata->nb_packets + 1); + } else { + nb_expected_requests = send_rtxdata->nb_packets / drop_every_n_packets; + fail_unless_equals_int (send_rtxdata->nb_packets, + receive_rtxdata->nb_packets); + } + + g_object_get (G_OBJECT (rtprtxsend), "num-rtx-requests", &nbrtxrequests, + NULL); + fail_unless_equals_int (nbrtxrequests, nb_expected_requests); + + g_object_get (G_OBJECT (rtprtxsend), "num-rtx-packets", &nbrtxpackets, NULL); + fail_unless_equals_int (nbrtxpackets, nb_expected_requests); + + g_object_get (G_OBJECT (rtprtxreceive), "num-rtx-requests", &nbrtxrequests, + NULL); + fail_unless_equals_int (nbrtxrequests, nb_expected_requests); + + g_object_get (G_OBJECT (rtprtxreceive), "num-rtx-packets", &nbrtxpackets, + NULL); + fail_unless_equals_int (nbrtxpackets, nb_expected_requests); + + g_object_get (G_OBJECT (rtprtxreceive), "num-rtx-assoc-packets", + &nbrtxpackets, NULL); + fail_unless_equals_int (nbrtxpackets, nb_expected_requests); + + state_res = gst_element_set_state (bin, GST_STATE_NULL); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); +} + +/* This test build the pipeline videotestsrc ! rtpvrawpay ! rtprtxsend ! rtprtxreceive ! fakesink + * and drop some buffer between rtprtxsend and rtprtxreceive + * Then it checks that every dropped packet has been re-sent and it checks that + * not too much requests has been sent. + */ +GST_START_TEST (test_drop_one_sender) +{ + GstElement *bin, *src, *rtppayloader, *rtprtxsend, *rtprtxreceive, *sink; + GstBus *bus; + gboolean res; + GstPad *srcpad, *sinkpad; + GstStreamConsistency *chk_1, *chk_2, *chk_3; + gint num_buffers = 20; + guint drop_every_n_packets = 0; + RTXSendData send_rtxdata; + RTXReceiveData receive_rtxdata; + gboolean eos = FALSE; + + GST_INFO ("preparing test"); + + /* build pipeline */ + bin = gst_pipeline_new ("pipeline"); + bus = gst_element_get_bus (bin); + gst_bus_add_signal_watch_full (bus, G_PRIORITY_HIGH); + + src = gst_element_factory_make ("videotestsrc", "src"); + g_object_set (src, "num-buffers", num_buffers, NULL); + rtppayloader = gst_element_factory_make ("rtpvrawpay", "rtppayloader"); + rtprtxsend = gst_element_factory_make ("rtprtxsend", "rtprtxsend"); + rtprtxreceive = gst_element_factory_make ("rtprtxreceive", "rtprtxreceive"); + sink = gst_element_factory_make ("fakesink", "sink"); + gst_bin_add_many (GST_BIN (bin), src, rtppayloader, rtprtxsend, rtprtxreceive, + sink, NULL); + + res = gst_element_link (src, rtppayloader); + fail_unless (res == TRUE, NULL); + res = gst_element_link (rtppayloader, rtprtxsend); + fail_unless (res == TRUE, NULL); + res = gst_element_link (rtprtxsend, rtprtxreceive); + fail_unless (res == TRUE, NULL); + res = gst_element_link (rtprtxreceive, sink); + fail_unless (res == TRUE, NULL); + + /* create consistency checkers for the pads */ + + srcpad = gst_element_get_static_pad (rtppayloader, "src"); + chk_1 = gst_consistency_checker_new (srcpad); + gst_object_unref (srcpad); + + srcpad = gst_element_get_static_pad (rtprtxsend, "src"); + gst_pad_add_probe (srcpad, + (GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_PUSH), + (GstPadProbeCallback) rtprtxsend_srcpad_probe, &send_rtxdata, NULL); + sinkpad = gst_pad_get_peer (srcpad); + fail_if (sinkpad == NULL); + chk_2 = gst_consistency_checker_new (sinkpad); + gst_object_unref (sinkpad); + gst_object_unref (srcpad); + + srcpad = gst_element_get_static_pad (rtprtxreceive, "src"); + gst_pad_add_probe (srcpad, + (GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_PUSH), + (GstPadProbeCallback) rtprtxreceive_srcpad_probe, &receive_rtxdata, NULL); + sinkpad = gst_pad_get_peer (srcpad); + fail_if (sinkpad == NULL); + chk_3 = gst_consistency_checker_new (sinkpad); + gst_object_unref (sinkpad); + gst_object_unref (srcpad); + + g_signal_connect (bus, "message::error", (GCallback) message_received, NULL); + g_signal_connect (bus, "message::warning", (GCallback) message_received, + NULL); + g_signal_connect (bus, "message::eos", (GCallback) message_received, &eos); + + for (drop_every_n_packets = 2; drop_every_n_packets < 10; + drop_every_n_packets++) { + start_test_drop_and_check_results (bin, rtppayloader, rtprtxsend, + rtprtxreceive, &send_rtxdata, &receive_rtxdata, drop_every_n_packets, + &eos); + } + + /* cleanup */ + gst_consistency_checker_free (chk_1); + gst_consistency_checker_free (chk_2); + gst_consistency_checker_free (chk_3); + gst_bus_remove_signal_watch (bus); + gst_object_unref (bus); + gst_object_unref (bin); +} + +GST_END_TEST; + +static void +message_received_multiple (GstBus * bus, GstMessage * message, gpointer data) +{ + GST_INFO ("bus message from \"%" GST_PTR_FORMAT "\": %" GST_PTR_FORMAT, + GST_MESSAGE_SRC (message), message); + + switch (message->type) { + case GST_MESSAGE_WARNING:{ + GError *gerror; + gchar *debug; + + gst_message_parse_warning (message, &gerror, &debug); + gst_object_default_error (GST_MESSAGE_SRC (message), gerror, debug); + g_error_free (gerror); + g_free (debug); + break; + } + case GST_MESSAGE_ERROR:{ + GError *gerror; + gchar *debug; + + gst_message_parse_error (message, &gerror, &debug); + gst_object_default_error (GST_MESSAGE_SRC (message), gerror, debug); + fail ("Error: %s / %s", gerror->message, debug); + g_error_free (gerror); + g_free (debug); + break; + } + default: + break; + } +} + +typedef struct +{ + guint count; + guint nb_packets; + guint drop_every_n_packets; + guint payload_type_master; + guint total_packets; +} RTXSendMultipleData; + +/* drop some packets */ +static GstPadProbeReturn +rtprtxsend_srcpad_probe_multiple (GstPad * pad, GstPadProbeInfo * info, + gpointer user_data) +{ + GstPadProbeReturn ret = GST_PAD_PROBE_OK; + + if (info->type == (GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_PUSH)) { + GstBuffer *buffer = GST_BUFFER (info->data); + RTXSendMultipleData *rtxdata = (RTXSendMultipleData *) user_data; + GstRTPBuffer rtp = GST_RTP_BUFFER_INIT; + guint payload_type = 0; + + gst_rtp_buffer_map (buffer, GST_MAP_READ, &rtp); + payload_type = gst_rtp_buffer_get_payload_type (&rtp); + + /* main stream packets */ + if (payload_type == rtxdata->payload_type_master) { + /* count packets of the main stream */ + ++rtxdata->nb_packets; + /* drop some packets */ + /* but make sure we never drop the last one, otherwise there + * will be nothing to trigger a retransmission. + */ + if (rtxdata->count < rtxdata->drop_every_n_packets || + rtxdata->nb_packets == rtxdata->total_packets) { + ++rtxdata->count; + } else { + /* drop a packet every 'rtxdata->count' packets */ + rtxdata->count = 1; + ret = GST_PAD_PROBE_DROP; + } + } else { + /* retransmission packets */ + } + + gst_rtp_buffer_unmap (&rtp); + } + + return ret; +} + +/* make sure every sources has sent all their buffers */ +static GstPadProbeReturn +source_srcpad_probe_multiple_drop_eos (GstPad * pad, GstPadProbeInfo * info, + gpointer user_data) +{ + GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info); + + if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) + return GST_PAD_PROBE_DROP; + else + return GST_PAD_PROBE_OK; +} + +typedef struct +{ + GHashTable *ssrc_to_nb_packets_map; + GHashTable *ssrc_to_seqnum_offset_map; + guint seqnum_offset; + + gint to_send; + volatile gint dropped_requests; + volatile gint received; + gboolean request_passed; +} RTXReceiveMultipleData; + +/* add one branch videotestsrc ! rtpvrawpay ! rtprtxsend ! queue ! funnel. */ +static RTXSendMultipleData * +add_sender (GstElement * bin, const gchar * src_name, + const gchar * payloader_name, guint payload_type_master, + guint payload_type_aux, RTXReceiveMultipleData * rtxdata) +{ + GstElement *src = NULL; + GstCaps *caps; + GstElement *rtppayloader = NULL; + GstElement *rtprtxsend = NULL; + GstElement *queue = NULL; + GstElement *funnel = NULL; + GstPad *srcpad = NULL; + gboolean res = FALSE; + RTXSendMultipleData *send_rtxdata = g_slice_new0 (RTXSendMultipleData); + gchar *pt_master; + GstStructure *pt_map; + + send_rtxdata->count = 1; + send_rtxdata->nb_packets = 0; + send_rtxdata->drop_every_n_packets = 0; + send_rtxdata->payload_type_master = payload_type_master; + send_rtxdata->total_packets = 25; + rtxdata->to_send += send_rtxdata->total_packets; + + src = gst_element_factory_make (src_name, NULL); + rtppayloader = gst_element_factory_make (payloader_name, NULL); + rtprtxsend = gst_element_factory_make ("rtprtxsend", NULL); + queue = gst_element_factory_make ("queue", NULL); + funnel = gst_bin_get_by_name (GST_BIN (bin), "funnel"); + + pt_master = g_strdup_printf ("%" G_GUINT32_FORMAT, payload_type_master); + pt_map = gst_structure_new ("application/x-rtp-pt-map", + pt_master, G_TYPE_UINT, payload_type_aux, NULL); + g_free (pt_master); + + g_object_set (src, "num-buffers", send_rtxdata->total_packets, NULL); + g_object_set (src, "is-live", TRUE, NULL); + g_object_set (rtppayloader, "pt", payload_type_master, NULL); + g_object_set (rtppayloader, "seqnum-offset", 1, NULL); + g_object_set (rtprtxsend, "payload-type-map", pt_map, NULL); + /* we want that every drop packet be resent fast */ + g_object_set (queue, "max-size-buffers", 1, NULL); + g_object_set (queue, "flush-on-eos", FALSE, NULL); + + gst_structure_free (pt_map); + + gst_bin_add_many (GST_BIN (bin), src, rtppayloader, rtprtxsend, queue, NULL); + + /* Make sure we have one buffer per frame, makes it easier to count! */ + caps = + gst_caps_from_string ("video/x-raw, width=20, height=10, framerate=30/1"); + res = gst_element_link_filtered (src, rtppayloader, caps); + gst_caps_unref (caps); + fail_unless (res == TRUE, NULL); + res = gst_element_link (rtppayloader, rtprtxsend); + fail_unless (res == TRUE, NULL); + res = gst_element_link (rtprtxsend, queue); + fail_unless (res == TRUE, NULL); + res = gst_element_link (queue, funnel); + fail_unless (res == TRUE, NULL); + gst_object_unref (funnel); + + /* to drop some packets */ + srcpad = gst_element_get_static_pad (rtprtxsend, "src"); + gst_pad_add_probe (srcpad, + (GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_PUSH), + (GstPadProbeCallback) rtprtxsend_srcpad_probe_multiple, send_rtxdata, + NULL); + gst_object_unref (srcpad); + + /* to make sure every sources has sent all their buffers */ + srcpad = gst_element_get_static_pad (src, "src"); + gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, + (GstPadProbeCallback) source_srcpad_probe_multiple_drop_eos, NULL, NULL); + gst_object_unref (srcpad); + + return send_rtxdata; +} + +static GstPadProbeReturn +rtprtxreceive_sinkpad_probe_check_drop (GstPad * pad, GstPadProbeInfo * info, + gpointer user_data) +{ + GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info); + RTXReceiveMultipleData *rtxdata = (RTXReceiveMultipleData *) user_data; + + if (GST_EVENT_TYPE (event) == GST_EVENT_CUSTOM_UPSTREAM && + gst_event_get_structure (event) != NULL && + gst_structure_has_name (gst_event_get_structure (event), + "GstRTPRetransmissionRequest")) + rtxdata->request_passed = TRUE; + + return GST_PAD_PROBE_OK; +} + +static gboolean +check_finished (RTXReceiveMultipleData * rtxdata) +{ + return (g_atomic_int_get (&rtxdata->received) >= (rtxdata->to_send - + g_atomic_int_get (&rtxdata->dropped_requests))); +} + +static GstPadProbeReturn +rtprtxreceive_srcpad_probe_multiple (GstPad * pad, GstPadProbeInfo * info, + gpointer user_data) +{ + if (info->type == (GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_PUSH)) { + GstBuffer *buffer = GST_BUFFER (info->data); + RTXReceiveMultipleData *rtxdata = (RTXReceiveMultipleData *) user_data; + GstRTPBuffer rtp = GST_RTP_BUFFER_INIT; + guint ssrc = 0; + guint seqnum = 0; + gpointer seqnum_prev = 0; + guint nb_packets = 0; + + gst_rtp_buffer_map (buffer, GST_MAP_READ, &rtp); + ssrc = gst_rtp_buffer_get_ssrc (&rtp); + seqnum = gst_rtp_buffer_get_seq (&rtp); + + g_atomic_int_inc (&rtxdata->received); + if (check_finished (rtxdata)) + g_main_context_wakeup (NULL); + + if (!g_hash_table_lookup_extended (rtxdata->ssrc_to_seqnum_offset_map, + GUINT_TO_POINTER (ssrc), NULL, &seqnum_prev)) { + /*In our test we take care to never drop the first buffer */ + g_hash_table_insert (rtxdata->ssrc_to_seqnum_offset_map, + GUINT_TO_POINTER (ssrc), GUINT_TO_POINTER (seqnum)); + g_hash_table_insert (rtxdata->ssrc_to_nb_packets_map, + GUINT_TO_POINTER (ssrc), GUINT_TO_POINTER (1)); + gst_rtp_buffer_unmap (&rtp); + return GST_PAD_PROBE_OK; + } + + + /* check if there is a dropped packet + * (in our test every packet arrived in increasing order) */ + if (seqnum > GPOINTER_TO_UINT (seqnum_prev) + rtxdata->seqnum_offset) { + GstPad *peerpad = gst_pad_get_peer (pad); + guint i = 0; + + /* ask retransmission of missing packets */ + for (i = GPOINTER_TO_UINT (seqnum_prev) + rtxdata->seqnum_offset; + i < seqnum; i += rtxdata->seqnum_offset) { + GstEvent *event = gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, + gst_structure_new ("GstRTPRetransmissionRequest", + "seqnum", G_TYPE_UINT, i, + "ssrc", G_TYPE_UINT, gst_rtp_buffer_get_ssrc (&rtp), + "payload-type", G_TYPE_UINT, + gst_rtp_buffer_get_payload_type (&rtp), + NULL)); + rtxdata->request_passed = FALSE; + gst_pad_push_event (peerpad, event); + if (!rtxdata->request_passed) { + g_atomic_int_inc (&rtxdata->dropped_requests); + if (check_finished (rtxdata)) + g_main_context_wakeup (NULL); + } + } + gst_object_unref (peerpad); + + g_hash_table_insert (rtxdata->ssrc_to_seqnum_offset_map, + GUINT_TO_POINTER (ssrc), GUINT_TO_POINTER (seqnum)); + } else if (seqnum == + GPOINTER_TO_UINT (seqnum_prev) + rtxdata->seqnum_offset) { + /* also update previous seqnum in this case */ + g_hash_table_insert (rtxdata->ssrc_to_seqnum_offset_map, + GUINT_TO_POINTER (ssrc), GUINT_TO_POINTER (seqnum)); + } else { + /* receive retransmited packet */ + } + + gst_rtp_buffer_unmap (&rtp); + + nb_packets = + GPOINTER_TO_UINT (g_hash_table_lookup (rtxdata->ssrc_to_nb_packets_map, + GUINT_TO_POINTER (ssrc))); + g_hash_table_insert (rtxdata->ssrc_to_nb_packets_map, + GUINT_TO_POINTER (ssrc), GUINT_TO_POINTER (++nb_packets)); + } + + return GST_PAD_PROBE_OK; +} + +static void +reset_rtx_send_data (RTXSendMultipleData * send_rtxdata, gpointer data) +{ + send_rtxdata->count = 1; + send_rtxdata->nb_packets = 0; + send_rtxdata->drop_every_n_packets = *(guint *) data; +} + +/* compute number of all packets sent by all sender */ +static void +compute_total_packets_sent (RTXSendMultipleData * send_rtxdata, gpointer data) +{ + guint *sum = (guint *) data; + *sum += send_rtxdata->nb_packets; +} + +/* compute number of all packets received by rtprtxreceive::src pad */ +static void +compute_total_packets_received (gpointer key, gpointer value, gpointer data) +{ + guint *sum = (guint *) data; + *sum += GPOINTER_TO_UINT (value); +} + +static void +start_test_drop_multiple_and_check_results (GstElement * bin, + GList * send_rtxdata_list, RTXReceiveMultipleData * receive_rtxdata, + guint drop_every_n_packets) +{ + GstStateChangeReturn state_res = GST_STATE_CHANGE_FAILURE; + GstElement *rtprtxreceive = + gst_bin_get_by_name (GST_BIN (bin), "rtprtxreceive"); + guint sum_all_packets_sent = 0; + guint sum_rtx_packets_sent = 0; + guint sum_all_packets_received = 0; + guint sum_rtx_packets_received = 0; + guint sum_rtx_assoc_packets_received = 0; + guint sum_rtx_dropped_packets_received = 0; + gdouble error_sent_recv = 0; + GstIterator *itr_elements = NULL; + gboolean done = FALSE; + GValue item = { 0 }; + GstElement *element = NULL; + gchar *name = NULL; + + GST_INFO ("starting test"); + + g_atomic_int_set (&receive_rtxdata->received, 0); + g_atomic_int_set (&receive_rtxdata->dropped_requests, 0); + + g_hash_table_remove_all (receive_rtxdata->ssrc_to_nb_packets_map); + g_hash_table_remove_all (receive_rtxdata->ssrc_to_seqnum_offset_map); + + g_list_foreach (send_rtxdata_list, (GFunc) reset_rtx_send_data, + &drop_every_n_packets); + + /* run pipeline */ + state_res = gst_element_set_state (bin, GST_STATE_PLAYING); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + state_res = gst_element_get_state (bin, NULL, NULL, GST_CLOCK_TIME_NONE); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + GST_INFO ("running main loop"); + while (!check_finished (receive_rtxdata)) + g_main_context_iteration (NULL, TRUE); + + /* check results */ + itr_elements = gst_bin_iterate_elements (GST_BIN (bin)); + done = FALSE; + while (!done) { + switch (gst_iterator_next (itr_elements, &item)) { + case GST_ITERATOR_OK: + element = GST_ELEMENT (g_value_get_object (&item)); + name = gst_element_get_name (element); + if (g_str_has_prefix (name, "rtprtxsend") > 0) { + guint nb_packets = 0; + g_object_get (G_OBJECT (element), "num-rtx-packets", &nb_packets, + NULL); + sum_rtx_packets_sent += nb_packets; + } + g_free (name); + g_value_reset (&item); + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (itr_elements); + break; + case GST_ITERATOR_ERROR: + done = TRUE; + break; + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + } + g_value_unset (&item); + gst_iterator_free (itr_elements); + + /* compute number of all packets sent by all sender */ + g_list_foreach (send_rtxdata_list, (GFunc) compute_total_packets_sent, + &sum_all_packets_sent); + + /* compute number of all packets received by rtprtxreceive::src pad */ + g_hash_table_foreach (receive_rtxdata->ssrc_to_nb_packets_map, + compute_total_packets_received, (gpointer) & sum_all_packets_received); + + sum_all_packets_received += + g_atomic_int_get (&receive_rtxdata->dropped_requests); + fail_if (sum_all_packets_sent < sum_all_packets_received); + + /* some packet are not received, I still have to figure out why + * but I suspect it comes from pipeline setup/shutdown + */ + if (sum_all_packets_sent != sum_all_packets_received) { + error_sent_recv = + 1 - sum_all_packets_received / (gdouble) sum_all_packets_sent; + fail_if (error_sent_recv > 0.30); + /* it should be 0% */ + } + + /* retrieve number of retransmit packets received by rtprtxreceive */ + g_object_get (G_OBJECT (rtprtxreceive), "num-rtx-packets", + &sum_rtx_packets_received, NULL); + + /* some of rtx packet are not received because the receiver avoids + * collision (= requests that have the same seqnum) + */ + fail_if (sum_rtx_packets_sent < sum_rtx_packets_received); + g_object_get (G_OBJECT (rtprtxreceive), "num-rtx-assoc-packets", + &sum_rtx_assoc_packets_received, NULL); + sum_rtx_dropped_packets_received = + sum_rtx_packets_received - sum_rtx_assoc_packets_received; + fail_unless_equals_int (sum_rtx_packets_sent, + sum_rtx_assoc_packets_received + sum_rtx_dropped_packets_received); + + gst_object_unref (rtprtxreceive); + state_res = gst_element_set_state (bin, GST_STATE_NULL); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); +} + +static void +free_rtx_send_data (gpointer data) +{ + g_slice_free (RTXSendMultipleData, data); +} + +/* This test build the pipeline funnel name=funnel + * videotestsrc ! rtpvrawpay ! rtprtxsend ! queue ! funnel. + * videotestsrc ! rtpvrawpay ! rtprtxsend ! queue ! funnel. + * N + * funnel. ! rtprtxreceive ! fakesink + * and drop some buffer just after each rtprtxsend + * Then it checks that every dropped packet has been re-sent and it checks + * that not too much requests has been sent. + */ +GST_START_TEST (test_drop_multiple_sender) +{ + GstElement *bin, *funnel, *rtprtxreceive, *sink; + GstBus *bus; + gboolean res; + GstPad *srcpad, *sinkpad; + guint drop_every_n_packets = 0; + GList *send_rtxdata_list = NULL; + RTXReceiveMultipleData receive_rtxdata = { NULL }; + GstStructure *pt_map; + + GST_INFO ("preparing test"); + + receive_rtxdata.ssrc_to_nb_packets_map = + g_hash_table_new (g_direct_hash, g_direct_equal); + receive_rtxdata.ssrc_to_seqnum_offset_map = + g_hash_table_new (g_direct_hash, g_direct_equal); + receive_rtxdata.seqnum_offset = 1; + + /* build pipeline */ + bin = gst_pipeline_new ("pipeline"); + bus = gst_element_get_bus (bin); + gst_bus_add_signal_watch_full (bus, G_PRIORITY_HIGH); + + funnel = gst_element_factory_make ("funnel", "funnel"); + rtprtxreceive = gst_element_factory_make ("rtprtxreceive", "rtprtxreceive"); + sink = gst_element_factory_make ("fakesink", "sink"); + g_object_set (sink, "sync", TRUE, NULL); + g_object_set (sink, "qos", FALSE, NULL); + gst_bin_add_many (GST_BIN (bin), funnel, rtprtxreceive, sink, NULL); + + send_rtxdata_list = + g_list_append (send_rtxdata_list, add_sender (bin, "videotestsrc", + "rtpvrawpay", 96, 121, &receive_rtxdata)); + send_rtxdata_list = + g_list_append (send_rtxdata_list, add_sender (bin, "videotestsrc", + "rtpvrawpay", 97, 122, &receive_rtxdata)); + send_rtxdata_list = + g_list_append (send_rtxdata_list, add_sender (bin, "videotestsrc", + "rtpvrawpay", 98, 123, &receive_rtxdata)); + send_rtxdata_list = + g_list_append (send_rtxdata_list, add_sender (bin, "videotestsrc", + "rtpvrawpay", 99, 124, &receive_rtxdata)); + + pt_map = gst_structure_new ("application/x-rtp-pt-map", + "96", G_TYPE_UINT, 121, "97", G_TYPE_UINT, 122, + "98", G_TYPE_UINT, 123, "99", G_TYPE_UINT, 124, NULL); + g_object_set (rtprtxreceive, "payload-type-map", pt_map, NULL); + gst_structure_free (pt_map); + + res = gst_element_link (funnel, rtprtxreceive); + fail_unless (res == TRUE, NULL); + res = gst_element_link (rtprtxreceive, sink); + fail_unless (res == TRUE, NULL); + + srcpad = gst_element_get_static_pad (rtprtxreceive, "src"); + gst_pad_add_probe (srcpad, + (GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_PUSH), + (GstPadProbeCallback) rtprtxreceive_srcpad_probe_multiple, + &receive_rtxdata, NULL); + gst_object_unref (srcpad); + + sinkpad = gst_element_get_static_pad (rtprtxreceive, "sink"); + gst_pad_add_probe (sinkpad, + GST_PAD_PROBE_TYPE_EVENT_UPSTREAM, + (GstPadProbeCallback) rtprtxreceive_sinkpad_probe_check_drop, + &receive_rtxdata, NULL); + gst_object_unref (sinkpad); + + g_signal_connect (bus, "message::error", + (GCallback) message_received_multiple, NULL); + g_signal_connect (bus, "message::warning", + (GCallback) message_received_multiple, NULL); + + for (drop_every_n_packets = 2; drop_every_n_packets < 10; + drop_every_n_packets++) { + start_test_drop_multiple_and_check_results (bin, send_rtxdata_list, + &receive_rtxdata, drop_every_n_packets); + } + + /* cleanup */ + + g_list_free_full (send_rtxdata_list, free_rtx_send_data); + g_hash_table_destroy (receive_rtxdata.ssrc_to_nb_packets_map); + g_hash_table_destroy (receive_rtxdata.ssrc_to_seqnum_offset_map); + + gst_bus_remove_signal_watch (bus); + gst_object_unref (bus); + gst_object_unref (bin); +} + +GST_END_TEST; + +struct GenerateTestBuffersData +{ + GstElement *src, *capsfilter, *payloader, *sink; + GMutex mutex; + GCond cond; + GList *buffers; + gint num_buffers; + guint last_seqnum; +}; + +static void +fakesink_handoff (GstElement * sink, GstBuffer * buf, GstPad * pad, + gpointer user_data) +{ + struct GenerateTestBuffersData *data = user_data; + + g_mutex_lock (&data->mutex); + + if (data->num_buffers > 0) + data->buffers = g_list_append (data->buffers, gst_buffer_ref (buf)); + + /* if we have collected enough buffers, unblock the main thread to stop */ + if (--data->num_buffers <= 0) + g_cond_signal (&data->cond); + + if (data->num_buffers == 0) + g_object_get (data->payloader, "seqnum", &data->last_seqnum, NULL); + + g_mutex_unlock (&data->mutex); +} + +static GList * +generate_test_buffers (const gint num_buffers, guint ssrc, guint * payload_type) +{ + GstElement *bin; + GstCaps *videotestsrc_caps; + gboolean res; + struct GenerateTestBuffersData data; + + fail_unless (num_buffers > 0); + + g_mutex_init (&data.mutex); + g_cond_init (&data.cond); + data.buffers = NULL; + data.num_buffers = num_buffers; + + bin = gst_pipeline_new (NULL); + data.src = gst_element_factory_make ("videotestsrc", NULL); + data.capsfilter = gst_element_factory_make ("capsfilter", NULL); + data.payloader = gst_element_factory_make ("rtpvrawpay", NULL); + data.sink = gst_element_factory_make ("fakesink", NULL); + + /* small frame size will cause vrawpay to generate exactly one rtp packet + * per video frame, which we need for the max-size-time test */ + videotestsrc_caps = + gst_caps_from_string + ("video/x-raw,format=I420,width=10,height=10,framerate=30/1"); + + g_object_set (data.src, "do-timestamp", TRUE, NULL); + g_object_set (data.capsfilter, "caps", videotestsrc_caps, NULL); + g_object_set (data.payloader, "seqnum-offset", 1, "ssrc", ssrc, NULL); + g_object_set (data.sink, "signal-handoffs", TRUE, NULL); + g_signal_connect (data.sink, "handoff", (GCallback) fakesink_handoff, &data); + + gst_caps_unref (videotestsrc_caps); + + gst_bin_add_many (GST_BIN (bin), data.src, data.capsfilter, data.payloader, + data.sink, NULL); + res = gst_element_link_many (data.src, data.capsfilter, data.payloader, + data.sink, NULL); + fail_unless_equals_int (res, TRUE); + + g_mutex_lock (&data.mutex); + ASSERT_SET_STATE (bin, GST_STATE_PLAYING, GST_STATE_CHANGE_ASYNC); + while (data.num_buffers > 0) + g_cond_wait (&data.cond, &data.mutex); + g_mutex_unlock (&data.mutex); + + g_object_get (data.payloader, "pt", payload_type, NULL); + + ASSERT_SET_STATE (bin, GST_STATE_NULL, GST_STATE_CHANGE_SUCCESS); + + fail_unless_equals_int (g_list_length (data.buffers), num_buffers); + fail_unless_equals_int (num_buffers, data.last_seqnum); + + g_mutex_clear (&data.mutex); + g_cond_clear (&data.cond); + gst_object_unref (bin); + + return data.buffers; +} + +static GstEvent * +create_rtx_event (guint seqnum, guint ssrc, guint payload_type) +{ + return gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, + gst_structure_new ("GstRTPRetransmissionRequest", + "seqnum", G_TYPE_UINT, seqnum, + "ssrc", G_TYPE_UINT, ssrc, + "payload-type", G_TYPE_UINT, payload_type, NULL)); +} + +static void +test_rtxsender_packet_retention (gboolean test_with_time) +{ + const gint num_buffers = test_with_time ? 30 : 10; + const gint half_buffers = num_buffers / 2; + const guint ssrc = 1234567; + const guint rtx_ssrc = 7654321; + const guint rtx_payload_type = 99; + GstStructure *pt_map; + GstStructure *ssrc_map; + GList *in_buffers, *node; + guint payload_type; + GstElement *rtxsend; + GstPad *srcpad, *sinkpad; + GstCaps *caps; + GstRTPBuffer rtp = GST_RTP_BUFFER_INIT; + gint i, j; + gboolean res; + + /* generate test data */ + in_buffers = generate_test_buffers (num_buffers, ssrc, &payload_type); + + /* clear the global buffers list, which we are going to use later */ + gst_check_drop_buffers (); + + /* setup element & pads */ + rtxsend = gst_check_setup_element ("rtprtxsend"); + + pt_map = gst_structure_new ("application/x-rtp-pt-map", + "96", G_TYPE_UINT, rtx_payload_type, NULL); + ssrc_map = gst_structure_new ("application/x-rtp-ssrc-map", + "1234567", G_TYPE_UINT, rtx_ssrc, NULL); + + /* in both cases we want the rtxsend queue to store 'half_buffers' + * amount of buffers at most. In max-size-packets mode, it's trivial. + * In max-size-time mode, we specify almost half a second, which is + * the equivalent of 15 frames in a 30fps video stream */ + g_object_set (rtxsend, + "max-size-packets", test_with_time ? 0 : half_buffers, + "max-size-time", test_with_time ? 499 : 0, + "payload-type-map", pt_map, "ssrc-map", ssrc_map, NULL); + gst_structure_free (pt_map); + gst_structure_free (ssrc_map); + + srcpad = gst_check_setup_src_pad (rtxsend, &srctemplate); + fail_unless_equals_int (gst_pad_set_active (srcpad, TRUE), TRUE); + + sinkpad = gst_check_setup_sink_pad (rtxsend, &sinktemplate); + fail_unless_equals_int (gst_pad_set_active (sinkpad, TRUE), TRUE); + + ASSERT_SET_STATE (rtxsend, GST_STATE_PLAYING, GST_STATE_CHANGE_SUCCESS); + + caps = gst_caps_from_string ("application/x-rtp, " + "media = (string)video, payload = (int)96, " + "ssrc = (uint)1234567, clock-rate = (int)90000, " + "encoding-name = (string)RAW"); + gst_check_setup_events (srcpad, rtxsend, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + + /* now push all buffers and request retransmission every time for all of them */ + node = in_buffers; + for (i = 1; i <= num_buffers; i++) { + GstBuffer *buffer = GST_BUFFER (node->data); + + /* verify that the original packets are correct */ + res = gst_rtp_buffer_map (buffer, GST_MAP_READ, &rtp); + fail_unless_equals_int (res, TRUE); + fail_unless_equals_int (gst_rtp_buffer_get_ssrc (&rtp), ssrc); + fail_unless_equals_int (gst_rtp_buffer_get_payload_type (&rtp), + payload_type); + fail_unless_equals_int (gst_rtp_buffer_get_seq (&rtp), i); + gst_rtp_buffer_unmap (&rtp); + + /* retransmit all the previous ones */ + for (j = 1; j < i; j++) { + /* synchronize with the chain() function of the "sinkpad" + * to make sure that rtxsend has pushed the rtx buffer out + * before continuing */ + GList *last_out_buffer = g_list_last (buffers); + g_mutex_lock (&check_mutex); + fail_unless_equals_int (gst_pad_push_event (sinkpad, + create_rtx_event (j, ssrc, payload_type)), TRUE); + /* wait for the rtx packet only if we expect the element + * to actually retransmit something */ + if (j >= MAX (i - half_buffers, 1)) { + guint64 end_time = g_get_monotonic_time () + G_TIME_SPAN_SECOND; + + while (last_out_buffer == g_list_last (buffers)) + fail_unless (g_cond_wait_until (&check_cond, &check_mutex, end_time)); + } + g_mutex_unlock (&check_mutex); + } + + /* push this one */ + gst_pad_push (srcpad, gst_buffer_ref (buffer)); + node = g_list_next (node); + } + + /* verify the result. buffers should be in this order (numbers are seqnums): + * 1, 1rtx, 2, 1rtx, 2rtx, 3, ... , 9, 5rtx, 6rtx, 7rtx, 8rtx, 9rtx, 10 */ + { + GstRTPBuffer orig_rtp = GST_RTP_BUFFER_INIT; + gint expected_rtx_requests, expected_rtx_packets; + gint real_rtx_requests, real_rtx_packets; + + /* verify statistics first */ + expected_rtx_packets = half_buffers * half_buffers + + ((half_buffers - 1) / 2.0f) * half_buffers; + for (i = 1, expected_rtx_requests = 0; i < num_buffers; i++) + expected_rtx_requests += i; + + g_object_get (rtxsend, "num-rtx-requests", &real_rtx_requests, + "num-rtx-packets", &real_rtx_packets, NULL); + fail_unless_equals_int (expected_rtx_requests, real_rtx_requests); + fail_unless_equals_int (expected_rtx_packets, real_rtx_packets); + + /* and the number of actual buffers that we were pushed out of rtxsend */ + fail_unless_equals_int (g_list_length (buffers), + num_buffers + expected_rtx_packets); + + node = buffers; + for (i = 1; i <= num_buffers; i++) { + /* verify the retransmission packets */ + for (j = MAX (i - half_buffers, 1); j < i; j++) { + GST_INFO ("checking %d, %d", i, j); + + res = gst_rtp_buffer_map (GST_BUFFER (node->data), GST_MAP_READ, &rtp); + fail_unless_equals_int (res, TRUE); + + fail_if (gst_rtp_buffer_get_ssrc (&rtp) == ssrc); + fail_unless_equals_int (gst_rtp_buffer_get_ssrc (&rtp), rtx_ssrc); + fail_unless_equals_int (gst_rtp_buffer_get_payload_type (&rtp), + rtx_payload_type); + fail_unless_equals_int (GST_READ_UINT16_BE (gst_rtp_buffer_get_payload (&rtp)), j); /* j == rtx seqnum */ + + /* open the original packet for this rtx packet and verify timestamps */ + res = gst_rtp_buffer_map (GST_BUFFER (g_list_nth_data (in_buffers, + j - 1)), GST_MAP_READ, &orig_rtp); + fail_unless_equals_int (res, TRUE); + fail_unless_equals_int (gst_rtp_buffer_get_timestamp (&orig_rtp), + gst_rtp_buffer_get_timestamp (&rtp)); + gst_rtp_buffer_unmap (&orig_rtp); + + gst_rtp_buffer_unmap (&rtp); + node = g_list_next (node); + } + + /* verify the normal rtp flow packet */ + res = gst_rtp_buffer_map (GST_BUFFER (node->data), GST_MAP_READ, &rtp); + fail_unless_equals_int (res, TRUE); + fail_unless_equals_int (gst_rtp_buffer_get_ssrc (&rtp), ssrc); + fail_unless_equals_int (gst_rtp_buffer_get_payload_type (&rtp), + payload_type); + fail_unless_equals_int (gst_rtp_buffer_get_seq (&rtp), i); + gst_rtp_buffer_unmap (&rtp); + node = g_list_next (node); + } + } + + g_list_free_full (in_buffers, (GDestroyNotify) gst_buffer_unref); + gst_check_drop_buffers (); + + gst_check_teardown_src_pad (rtxsend); + gst_check_teardown_sink_pad (rtxsend); + gst_check_teardown_element (rtxsend); +} + +GST_START_TEST (test_rtxsender_max_size_packets) +{ + test_rtxsender_packet_retention (FALSE); +} + +GST_END_TEST; + +GST_START_TEST (test_rtxsender_max_size_time) +{ + test_rtxsender_packet_retention (TRUE); +} + +GST_END_TEST; + +static void +compare_rtp_packets (GstBuffer * a, GstBuffer * b) +{ + GstRTPBuffer rtp_a = GST_RTP_BUFFER_INIT; + GstRTPBuffer rtp_b = GST_RTP_BUFFER_INIT; + + gst_rtp_buffer_map (a, GST_MAP_READ, &rtp_a); + gst_rtp_buffer_map (b, GST_MAP_READ, &rtp_b); + + fail_unless_equals_int (gst_rtp_buffer_get_header_len (&rtp_a), + gst_rtp_buffer_get_header_len (&rtp_b)); + fail_unless_equals_int (gst_rtp_buffer_get_version (&rtp_a), + gst_rtp_buffer_get_version (&rtp_b)); + fail_unless_equals_int (gst_rtp_buffer_get_ssrc (&rtp_a), + gst_rtp_buffer_get_ssrc (&rtp_b)); + fail_unless_equals_int (gst_rtp_buffer_get_seq (&rtp_a), + gst_rtp_buffer_get_seq (&rtp_b)); + fail_unless_equals_int (gst_rtp_buffer_get_csrc_count (&rtp_a), + gst_rtp_buffer_get_csrc_count (&rtp_b)); + fail_unless_equals_int (gst_rtp_buffer_get_marker (&rtp_a), + gst_rtp_buffer_get_marker (&rtp_b)); + fail_unless_equals_int (gst_rtp_buffer_get_payload_type (&rtp_a), + gst_rtp_buffer_get_payload_type (&rtp_b)); + fail_unless_equals_int (gst_rtp_buffer_get_timestamp (&rtp_a), + gst_rtp_buffer_get_timestamp (&rtp_b)); + fail_unless_equals_int (gst_rtp_buffer_get_extension (&rtp_a), + gst_rtp_buffer_get_extension (&rtp_b)); + + fail_unless_equals_int (gst_rtp_buffer_get_payload_len (&rtp_a), + gst_rtp_buffer_get_payload_len (&rtp_b)); + fail_unless_equals_int (memcmp (gst_rtp_buffer_get_payload (&rtp_a), + gst_rtp_buffer_get_payload (&rtp_b), + gst_rtp_buffer_get_payload_len (&rtp_a)), 0); + + gst_rtp_buffer_unmap (&rtp_a); + gst_rtp_buffer_unmap (&rtp_b); +} + +GST_START_TEST (test_rtxreceive_data_reconstruction) +{ + const guint ssrc = 1234567; + GList *in_buffers; + guint payload_type; + GstElement *rtxsend, *rtxrecv; + GstPad *srcpad, *sinkpad; + GstCaps *caps; + GstBuffer *buffer; + GstStructure *pt_map; + + /* generate test data */ + in_buffers = generate_test_buffers (1, ssrc, &payload_type); + + /* clear the global buffers list, which we are going to use later */ + gst_check_drop_buffers (); + + /* setup element & pads */ + rtxsend = gst_check_setup_element ("rtprtxsend"); + rtxrecv = gst_check_setup_element ("rtprtxreceive"); + + pt_map = gst_structure_new ("application/x-rtp-pt-map", + "96", G_TYPE_UINT, 99, NULL); + g_object_set (rtxsend, "payload-type-map", pt_map, NULL); + g_object_set (rtxrecv, "payload-type-map", pt_map, NULL); + gst_structure_free (pt_map); + + fail_unless_equals_int (gst_element_link (rtxsend, rtxrecv), TRUE); + + srcpad = gst_check_setup_src_pad (rtxsend, &srctemplate); + fail_unless_equals_int (gst_pad_set_active (srcpad, TRUE), TRUE); + + sinkpad = gst_check_setup_sink_pad (rtxrecv, &sinktemplate); + fail_unless_equals_int (gst_pad_set_active (sinkpad, TRUE), TRUE); + + ASSERT_SET_STATE (rtxsend, GST_STATE_PLAYING, GST_STATE_CHANGE_SUCCESS); + ASSERT_SET_STATE (rtxrecv, GST_STATE_PLAYING, GST_STATE_CHANGE_SUCCESS); + + caps = gst_caps_from_string ("application/x-rtp, " + "media = (string)video, payload = (int)96, " + "ssrc = (uint)1234567, clock-rate = (int)90000, " + "encoding-name = (string)RAW"); + gst_check_setup_events (srcpad, rtxsend, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + + /* push buffer */ + buffer = gst_buffer_ref (GST_BUFFER (in_buffers->data)); + fail_unless_equals_int (gst_pad_push (srcpad, buffer), GST_FLOW_OK); + + /* push retransmission request */ + { + GList *last_out_buffer; + guint64 end_time; + gboolean res; + + /* synchronize with the chain() function of the "sinkpad" + * to make sure that rtxsend has pushed the rtx buffer out + * before continuing */ + last_out_buffer = g_list_last (buffers); + g_mutex_lock (&check_mutex); + fail_unless_equals_int (gst_pad_push_event (sinkpad, + create_rtx_event (1, ssrc, payload_type)), TRUE); + end_time = g_get_monotonic_time () + G_TIME_SPAN_SECOND; + do + res = g_cond_wait_until (&check_cond, &check_mutex, end_time); + while (res == TRUE && last_out_buffer == g_list_last (buffers)); + fail_unless_equals_int (res, TRUE); + g_mutex_unlock (&check_mutex); + } + + /* verify */ + fail_unless_equals_int (g_list_length (buffers), 2); + compare_rtp_packets (GST_BUFFER (buffers->data), + GST_BUFFER (buffers->next->data)); + + /* cleanup */ + g_list_free_full (in_buffers, (GDestroyNotify) gst_buffer_unref); + gst_check_drop_buffers (); + + gst_check_teardown_src_pad (rtxsend); + gst_check_teardown_sink_pad (rtxrecv); + gst_element_unlink (rtxsend, rtxrecv); + gst_check_teardown_element (rtxsend); + gst_check_teardown_element (rtxrecv); +} + +GST_END_TEST; + +static Suite * +rtprtx_suite (void) +{ + Suite *s = suite_create ("rtprtx"); + TCase *tc_chain = tcase_create ("general"); + + tcase_set_timeout (tc_chain, 120); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_push_forward_seq); + tcase_add_test (tc_chain, test_drop_one_sender); + tcase_add_test (tc_chain, test_drop_multiple_sender); + tcase_add_test (tc_chain, test_rtxsender_max_size_packets); + tcase_add_test (tc_chain, test_rtxsender_max_size_time); + tcase_add_test (tc_chain, test_rtxreceive_data_reconstruction); + + return s; +} + +GST_CHECK_MAIN (rtprtx); diff --git a/tests/check/elements/rtpsession.c b/tests/check/elements/rtpsession.c new file mode 100755 index 0000000..7c7b568 --- /dev/null +++ b/tests/check/elements/rtpsession.c @@ -0,0 +1,597 @@ +/* GStreamer + * + * unit test for gstrtpsession + * + * Copyright (C) <2009> Wim Taymans <wim.taymans@gmail.com> + * Copyright (C) 2013 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <gst/check/gstcheck.h> +#include <gst/check/gsttestclock.h> + +#include <gst/rtp/gstrtpbuffer.h> +#include <gst/rtp/gstrtcpbuffer.h> + +static const guint payload_size = 160; +static const guint clock_rate = 8000; +static const guint payload_type = 0; + +typedef struct +{ + GstElement *session; + GstPad *src, *rtcp_sink, *rtpsrc; + GstClock *clock; + GAsyncQueue *rtcp_queue; +} TestData; + +static GstCaps * +generate_caps (void) +{ + return gst_caps_new_simple ("application/x-rtp", + "clock-rate", G_TYPE_INT, clock_rate, + "payload-type", G_TYPE_INT, payload_type, NULL); +} + +static GstBuffer * +generate_test_buffer (GstClockTime gst_ts, + gboolean marker_bit, guint seq_num, guint32 rtp_ts, guint ssrc) +{ + GstBuffer *buf; + guint8 *payload; + guint i; + GstRTPBuffer rtp = GST_RTP_BUFFER_INIT; + + buf = gst_rtp_buffer_new_allocate (payload_size, 0, 0); + GST_BUFFER_DTS (buf) = gst_ts; + GST_BUFFER_PTS (buf) = gst_ts; + + gst_rtp_buffer_map (buf, GST_MAP_READWRITE, &rtp); + gst_rtp_buffer_set_payload_type (&rtp, payload_type); + gst_rtp_buffer_set_marker (&rtp, marker_bit); + gst_rtp_buffer_set_seq (&rtp, seq_num); + gst_rtp_buffer_set_timestamp (&rtp, rtp_ts); + gst_rtp_buffer_set_ssrc (&rtp, ssrc); + + payload = gst_rtp_buffer_get_payload (&rtp); + for (i = 0; i < payload_size; i++) + payload[i] = 0xff; + + gst_rtp_buffer_unmap (&rtp); + + return buf; +} + +static GstFlowReturn +test_sink_pad_chain_cb (GstPad * pad, GstObject * parent, GstBuffer * buffer) +{ + TestData *data = gst_pad_get_element_private (pad); + g_async_queue_push (data->rtcp_queue, buffer); + GST_DEBUG ("chained"); + return GST_FLOW_OK; +} + +static GstCaps * +pt_map_requested (GstElement * elemen, guint pt, gpointer data) +{ + return generate_caps (); +} + +static void +destroy_testharness (TestData * data) +{ + g_assert_cmpint (gst_element_set_state (data->session, GST_STATE_NULL), + ==, GST_STATE_CHANGE_SUCCESS); + gst_object_unref (data->session); + data->session = NULL; + + gst_object_unref (data->src); + data->src = NULL; + + gst_object_unref (data->rtcp_sink); + data->rtcp_sink = NULL; + + gst_object_unref (data->rtpsrc); + data->rtpsrc = NULL; + + gst_object_unref (data->clock); + data->clock = NULL; + + g_async_queue_unref (data->rtcp_queue); + data->rtcp_queue = NULL; +} + +static void +setup_testharness (TestData * data, gboolean session_as_sender) +{ + GstPad *rtp_sink_pad, *rtcp_src_pad, *rtp_src_pad; + GstSegment seg; + GstMiniObject *obj; + GstCaps *caps; + + data->clock = gst_test_clock_new (); + GST_DEBUG ("Setting default system clock to test clock"); + gst_system_clock_set_default (data->clock); + g_assert (data->clock); + gst_test_clock_set_time (GST_TEST_CLOCK (data->clock), 0); + + data->session = gst_element_factory_make ("rtpsession", NULL); + g_signal_connect (data->session, "request-pt-map", + (GCallback) pt_map_requested, data); + g_assert (data->session); + gst_element_set_clock (data->session, data->clock); + g_assert_cmpint (gst_element_set_state (data->session, + GST_STATE_PLAYING), !=, GST_STATE_CHANGE_FAILURE); + + data->rtcp_queue = + g_async_queue_new_full ((GDestroyNotify) gst_mini_object_unref); + + /* link in the test source-pad */ + data->src = gst_pad_new ("src", GST_PAD_SRC); + g_assert (data->src); + rtp_sink_pad = gst_element_get_request_pad (data->session, + session_as_sender ? "send_rtp_sink" : "recv_rtp_sink"); + g_assert (rtp_sink_pad); + g_assert_cmpint (gst_pad_link (data->src, rtp_sink_pad), ==, GST_PAD_LINK_OK); + gst_object_unref (rtp_sink_pad); + + data->rtpsrc = gst_pad_new ("sink", GST_PAD_SINK); + g_assert (data->rtpsrc); + rtp_src_pad = gst_element_get_static_pad (data->session, + session_as_sender ? "send_rtp_src" : "recv_rtp_src"); + g_assert (rtp_src_pad); + g_assert_cmpint (gst_pad_link (rtp_src_pad, data->rtpsrc), ==, + GST_PAD_LINK_OK); + gst_object_unref (rtp_src_pad); + + /* link in the test sink-pad */ + data->rtcp_sink = gst_pad_new ("sink", GST_PAD_SINK); + g_assert (data->rtcp_sink); + gst_pad_set_element_private (data->rtcp_sink, data); + caps = generate_caps (); + gst_pad_set_caps (data->rtcp_sink, caps); + gst_pad_set_chain_function (data->rtcp_sink, test_sink_pad_chain_cb); + rtcp_src_pad = gst_element_get_request_pad (data->session, "send_rtcp_src"); + g_assert (rtcp_src_pad); + g_assert_cmpint (gst_pad_link (rtcp_src_pad, data->rtcp_sink), ==, + GST_PAD_LINK_OK); + gst_object_unref (rtcp_src_pad); + + g_assert (gst_pad_set_active (data->src, TRUE)); + g_assert (gst_pad_set_active (data->rtcp_sink, TRUE)); + + gst_segment_init (&seg, GST_FORMAT_TIME); + gst_pad_push_event (data->src, gst_event_new_stream_start ("stream0")); + gst_pad_set_caps (data->src, caps); + gst_pad_push_event (data->src, gst_event_new_segment (&seg)); + gst_caps_unref (caps); + + while ((obj = g_async_queue_try_pop (data->rtcp_queue))) + gst_mini_object_unref (obj); +} + +GST_START_TEST (test_multiple_ssrc_rr) +{ + TestData data; + GstFlowReturn res; + GstClockID id, tid; + GstBuffer *in_buf, *out_buf; + GstRTCPBuffer rtcp = GST_RTCP_BUFFER_INIT; + GstRTCPPacket rtcp_packet; + int i; + guint32 ssrc, exthighestseq, jitter, lsr, dlsr; + gint32 packetslost; + guint8 fractionlost; + + setup_testharness (&data, FALSE); + + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), 10 * GST_MSECOND); + + for (i = 0; i < 5; i++) { + GST_DEBUG ("Push %i", i); + in_buf = + generate_test_buffer (i * 20 * GST_MSECOND, FALSE, i, i * 20, + 0x01BADBAD); + res = gst_pad_push (data.src, in_buf); + fail_unless (res == GST_FLOW_OK || res == GST_FLOW_FLUSHING); + + + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (data.clock), &id); + tid = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (data.clock)); + gst_clock_id_unref (id); + if (tid) + gst_clock_id_unref (tid); + + in_buf = + generate_test_buffer (i * 20 * GST_MSECOND, FALSE, i, i * 20, + 0xDEADBEEF); + res = gst_pad_push (data.src, in_buf); + fail_unless (res == GST_FLOW_OK || res == GST_FLOW_FLUSHING); + + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (data.clock), &id); + tid = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (data.clock)); + GST_DEBUG ("pushed %i", i); + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), + gst_clock_id_get_time (id)); + gst_clock_id_unref (id); + if (tid) + gst_clock_id_unref (tid); + } + + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), + gst_clock_id_get_time (id) + (2 * GST_SECOND)); + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (data.clock), &id); + tid = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (data.clock)); + gst_clock_id_unref (id); + gst_clock_id_unref (tid); + + out_buf = g_async_queue_pop (data.rtcp_queue); + g_assert (out_buf != NULL); + g_assert (gst_rtcp_buffer_validate (out_buf)); + gst_rtcp_buffer_map (out_buf, GST_MAP_READ, &rtcp); + g_assert (gst_rtcp_buffer_get_first_packet (&rtcp, &rtcp_packet)); + g_assert (gst_rtcp_packet_get_type (&rtcp_packet) == GST_RTCP_TYPE_RR); + g_assert_cmpint (gst_rtcp_packet_get_rb_count (&rtcp_packet), ==, 2); + + gst_rtcp_packet_get_rb (&rtcp_packet, 0, &ssrc, &fractionlost, &packetslost, + &exthighestseq, &jitter, &lsr, &dlsr); + + g_assert_cmpint (ssrc, ==, 0x01BADBAD); + + gst_rtcp_packet_get_rb (&rtcp_packet, 1, &ssrc, &fractionlost, &packetslost, + &exthighestseq, &jitter, &lsr, &dlsr); + g_assert_cmpint (ssrc, ==, 0xDEADBEEF); + gst_rtcp_buffer_unmap (&rtcp); + gst_buffer_unref (out_buf); + + destroy_testharness (&data); +} + +GST_END_TEST; + +/* This verifies that rtpsession will correctly place RBs round-robin + * across multiple SRs when there are too many senders that their RBs + * do not fit in one SR */ +GST_START_TEST (test_multiple_senders_roundrobin_rbs) +{ + TestData data; + GstFlowReturn res; + GstClockID id, tid; + GstBuffer *buf; + GstRTCPBuffer rtcp = GST_RTCP_BUFFER_INIT; + GstRTCPPacket rtcp_packet; + GstClockTime time; + gint queue_length; + gint i, j, k; + guint32 ssrc; + GHashTable *sr_ssrcs, *rb_ssrcs, *tmp_set; + + setup_testharness (&data, TRUE); + + /* only the RTCP thread waits on the clock */ + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (data.clock), &id); + + for (i = 0; i < 2; i++) { /* cycles between SR reports */ + for (j = 0; j < 5; j++) { /* packets per ssrc */ + gint seq = (i * 5) + j; + GST_DEBUG ("Push %i", seq); + + gst_test_clock_advance_time (GST_TEST_CLOCK (data.clock), + 200 * GST_MSECOND); + + for (k = 0; k < 35; k++) { /* number of ssrcs */ + buf = + generate_test_buffer (seq * 200 * GST_MSECOND, FALSE, seq, + seq * 200, 10000 + k); + res = gst_pad_push (data.src, buf); + fail_unless (res == GST_FLOW_OK || res == GST_FLOW_FLUSHING); + } + + GST_DEBUG ("pushed %i", seq); + } + + queue_length = g_async_queue_length (data.rtcp_queue); + + do { + /* crank the RTCP pad thread */ + time = gst_clock_id_get_time (id); + GST_DEBUG ("Advancing time to %" GST_TIME_FORMAT, GST_TIME_ARGS (time)); + gst_test_clock_set_time (GST_TEST_CLOCK (data.clock), time); + tid = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (data.clock)); + fail_unless_equals_pointer (tid, id); + gst_clock_id_unref (id); + gst_clock_id_unref (tid); + + /* wait for the RTCP pad thread to output its data + * and start waiting on the next timeout */ + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (data.clock), + &id); + + /* and retry as long as there are no new RTCP packets out, + * because the RTCP thread may randomly decide to reschedule + * the RTCP timeout for later */ + } while (g_async_queue_length (data.rtcp_queue) == queue_length); + + GST_DEBUG ("RTCP timeout processed"); + } + gst_clock_id_unref (id); + + sr_ssrcs = g_hash_table_new (g_direct_hash, g_direct_equal); + rb_ssrcs = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, + (GDestroyNotify) g_hash_table_unref); + + /* verify the rtcp packets */ + for (i = 0; i < 2 * 35; i++) { + guint expected_rb_count = (i < 35) ? GST_RTCP_MAX_RB_COUNT : + (35 - GST_RTCP_MAX_RB_COUNT - 1); + + GST_DEBUG ("pop %d", i); + + buf = g_async_queue_pop (data.rtcp_queue); + g_assert (buf != NULL); + g_assert (gst_rtcp_buffer_validate (buf)); + + gst_rtcp_buffer_map (buf, GST_MAP_READ, &rtcp); + g_assert (gst_rtcp_buffer_get_first_packet (&rtcp, &rtcp_packet)); + g_assert_cmpint (gst_rtcp_packet_get_type (&rtcp_packet), ==, + GST_RTCP_TYPE_SR); + + gst_rtcp_packet_sr_get_sender_info (&rtcp_packet, &ssrc, NULL, NULL, NULL, + NULL); + g_assert_cmpint (ssrc, >=, 10000); + g_assert_cmpint (ssrc, <=, 10035); + g_hash_table_add (sr_ssrcs, GUINT_TO_POINTER (ssrc)); + + /* inspect the RBs */ + g_assert_cmpint (gst_rtcp_packet_get_rb_count (&rtcp_packet), ==, + expected_rb_count); + + if (i < 35) { + tmp_set = g_hash_table_new (g_direct_hash, g_direct_equal); + g_hash_table_insert (rb_ssrcs, GUINT_TO_POINTER (ssrc), tmp_set); + } else { + tmp_set = g_hash_table_lookup (rb_ssrcs, GUINT_TO_POINTER (ssrc)); + g_assert (tmp_set); + } + + for (j = 0; j < expected_rb_count; j++) { + gst_rtcp_packet_get_rb (&rtcp_packet, j, &ssrc, NULL, NULL, + NULL, NULL, NULL, NULL); + g_assert_cmpint (ssrc, >=, 10000); + g_assert_cmpint (ssrc, <=, 10035); + g_hash_table_add (tmp_set, GUINT_TO_POINTER (ssrc)); + } + + gst_rtcp_buffer_unmap (&rtcp); + gst_buffer_unref (buf); + + /* cycle done, verify all ssrcs have issued SR reports */ + if ((i + 1) == 35 || (i + 1) == (2 * 35)) { + g_assert_cmpint (g_hash_table_size (sr_ssrcs), ==, 35); + g_hash_table_remove_all (sr_ssrcs); + } + } + + /* now verify all other ssrcs have been reported on each ssrc's SR */ + g_assert_cmpint (g_hash_table_size (rb_ssrcs), ==, 35); + for (i = 10000; i < 10035; i++) { + tmp_set = g_hash_table_lookup (rb_ssrcs, GUINT_TO_POINTER (i)); + g_assert (tmp_set); + /* SR contains RBs for each other ssrc except the ssrc of the SR */ + g_assert_cmpint (g_hash_table_size (tmp_set), ==, 34); + g_assert (!g_hash_table_contains (tmp_set, GUINT_TO_POINTER (i))); + } + + g_hash_table_unref (sr_ssrcs); + g_hash_table_unref (rb_ssrcs); + + destroy_testharness (&data); +} + +GST_END_TEST; + +static void +crank_rtcp_thread (TestData * data, GstClockTime * time, GstClockID * id) +{ + gint queue_length; + GstClockID *tid; + + queue_length = g_async_queue_length (data->rtcp_queue); + do { + *time = gst_clock_id_get_time (*id); + GST_DEBUG ("Advancing time to %" GST_TIME_FORMAT, GST_TIME_ARGS (*time)); + if (*time > gst_clock_get_time (data->clock)) + gst_test_clock_set_time (GST_TEST_CLOCK (data->clock), *time); + tid = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (data->clock)); + fail_unless_equals_pointer (tid, *id); + + gst_clock_id_unref (tid); + gst_clock_id_unref (*id); + *id = NULL; + + /* wait for the RTCP pad thread to output its data + * and start waiting on the next timeout */ + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (data->clock), id); + + /* and retry as long as there are no new RTCP packets out, + * because the RTCP thread may randomly decide to reschedule + * the RTCP timeout for later */ + } while (g_async_queue_length (data->rtcp_queue) == queue_length); +} + +GST_START_TEST (test_internal_sources_timeout) +{ + TestData data; + GstClockID id; + GstClockTime time; + GObject *internal_session; + guint internal_ssrc; + guint32 ssrc; + GstBuffer *buf; + GstRTCPBuffer rtcp = GST_RTCP_BUFFER_INIT; + GstRTCPPacket rtcp_packet; + GstFlowReturn res; + gint i, j; + + setup_testharness (&data, TRUE); + g_object_get (data.session, "internal-session", &internal_session, NULL); + g_object_set (internal_session, "internal-ssrc", 0xDEADBEEF, NULL); + + /* only the RTCP thread waits on the clock */ + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (data.clock), &id); + + /* crank the RTCP pad thread until it creates a RR for its internal-ssrc + * source, since we have not pushed any RTP packets and it doesn't have + * any other source available */ + crank_rtcp_thread (&data, &time, &id); + + g_object_get (internal_session, "internal-ssrc", &internal_ssrc, NULL); + g_assert_cmpint (internal_ssrc, ==, 0xDEADBEEF); + + /* verify that rtpsession has sent RR for an internally-created + * RTPSource that is using the internal-ssrc */ + buf = g_async_queue_pop (data.rtcp_queue); + g_assert (buf != NULL); + g_assert (gst_rtcp_buffer_validate (buf)); + gst_rtcp_buffer_map (buf, GST_MAP_READ, &rtcp); + g_assert (gst_rtcp_buffer_get_first_packet (&rtcp, &rtcp_packet)); + g_assert_cmpint (gst_rtcp_packet_get_type (&rtcp_packet), ==, + GST_RTCP_TYPE_RR); + ssrc = gst_rtcp_packet_rr_get_ssrc (&rtcp_packet); + g_assert_cmpint (ssrc, ==, internal_ssrc); + gst_rtcp_buffer_unmap (&rtcp); + gst_buffer_unref (buf); + + /* ok, now let's push some RTP packets */ + for (i = 1; i < 4; i++) { + gst_test_clock_advance_time (GST_TEST_CLOCK (data.clock), + 200 * GST_MSECOND); + buf = + generate_test_buffer (time + i * 200 * GST_MSECOND, FALSE, i, i * 200, + 0x01BADBAD); + res = gst_pad_push (data.src, buf); + fail_unless (res == GST_FLOW_OK || res == GST_FLOW_FLUSHING); + } + + /* internal ssrc must have changed already */ + g_object_get (internal_session, "internal-ssrc", &internal_ssrc, NULL); + g_assert_cmpint (ssrc, !=, internal_ssrc); + g_assert_cmpint (internal_ssrc, ==, 0x01BADBAD); + + /* wait for SR */ + crank_rtcp_thread (&data, &time, &id); + + /* verify SR and RR */ + j = 0; + for (i = 0; i < 2; i++) { + buf = g_async_queue_pop (data.rtcp_queue); + g_assert (buf != NULL); + g_assert (gst_rtcp_buffer_validate (buf)); + gst_rtcp_buffer_map (buf, GST_MAP_READ, &rtcp); + g_assert (gst_rtcp_buffer_get_first_packet (&rtcp, &rtcp_packet)); + if (gst_rtcp_packet_get_type (&rtcp_packet) == GST_RTCP_TYPE_SR) { + gst_rtcp_packet_sr_get_sender_info (&rtcp_packet, &ssrc, NULL, NULL, NULL, + NULL); + g_assert_cmpint (ssrc, ==, internal_ssrc); + g_assert_cmpint (ssrc, ==, 0x01BADBAD); + j |= 0x1; + } else if (gst_rtcp_packet_get_type (&rtcp_packet) == GST_RTCP_TYPE_RR) { + ssrc = gst_rtcp_packet_rr_get_ssrc (&rtcp_packet); + g_assert_cmpint (ssrc, !=, internal_ssrc); + g_assert_cmpint (ssrc, ==, 0xDEADBEEF); + j |= 0x2; + } + gst_rtcp_buffer_unmap (&rtcp); + gst_buffer_unref (buf); + } + g_assert_cmpint (j, ==, 0x3); /* verify we got both SR and RR */ + + /* go 30 seconds in the future and observe both sources timing out: + * 0xDEADBEEF -> BYE, 0x01BADBAD -> becomes receiver only */ + gst_test_clock_advance_time (GST_TEST_CLOCK (data.clock), 30 * GST_SECOND); + crank_rtcp_thread (&data, &time, &id); + + /* verify BYE and RR */ + j = 0; + for (i = 0; i < 2; i++) { + buf = g_async_queue_pop (data.rtcp_queue); + g_assert (buf != NULL); + g_assert (gst_rtcp_buffer_validate (buf)); + gst_rtcp_buffer_map (buf, GST_MAP_READ, &rtcp); + + g_assert (gst_rtcp_buffer_get_first_packet (&rtcp, &rtcp_packet)); + g_assert_cmpint (gst_rtcp_packet_get_type (&rtcp_packet), ==, + GST_RTCP_TYPE_RR); + ssrc = gst_rtcp_packet_rr_get_ssrc (&rtcp_packet); + if (ssrc == 0x01BADBAD) { + j |= 0x1; + g_assert_cmpint (ssrc, ==, internal_ssrc); + /* 2 => RR, SDES. There is no BYE here */ + g_assert_cmpint (gst_rtcp_buffer_get_packet_count (&rtcp), ==, 2); + } else if (ssrc == 0xDEADBEEF) { + j |= 0x2; + g_assert_cmpint (ssrc, !=, internal_ssrc); + /* 3 => RR, SDES, BYE */ + g_assert_cmpint (gst_rtcp_buffer_get_packet_count (&rtcp), ==, 3); + g_assert (gst_rtcp_packet_move_to_next (&rtcp_packet)); + g_assert (gst_rtcp_packet_move_to_next (&rtcp_packet)); + g_assert_cmpint (gst_rtcp_packet_get_type (&rtcp_packet), ==, + GST_RTCP_TYPE_BYE); + } + + gst_rtcp_buffer_unmap (&rtcp); + gst_buffer_unref (buf); + } + g_assert_cmpint (j, ==, 0x3); /* verify we got both BYE and RR */ + gst_clock_id_unref (id); + + g_object_unref (internal_session); + destroy_testharness (&data); +} + +GST_END_TEST; + +static Suite * +gstrtpsession_suite (void) +{ + Suite *s = suite_create ("rtpsession"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_multiple_ssrc_rr); + tcase_add_test (tc_chain, test_multiple_senders_roundrobin_rbs); + tcase_add_test (tc_chain, test_internal_sources_timeout); + + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = gstrtpsession_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/shapewipe.c b/tests/check/elements/shapewipe.c new file mode 100755 index 0000000..ab90084 --- /dev/null +++ b/tests/check/elements/shapewipe.c @@ -0,0 +1,316 @@ +/* GStreamer + * + * Copyright (C) 2009 Sebastian Dröge <sebastian.droege@collabora.co.uk> + * + * 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 <unistd.h> + +#include <gst/check/gstcheck.h> + +gboolean have_eos = FALSE; + +/* 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 */ +GstPad *myvideosrcpad, *mymasksrcpad, *mysinkpad; + + +#define SHAPEWIPE_VIDEO_CAPS_STRING \ + "video/x-raw, " \ + "format = (string)AYUV, " \ + "width = 400, " \ + "height = 400, " \ + "framerate = 0/1" + +#define SHAPEWIPE_MASK_CAPS_STRING \ + "video/x-raw, " \ + "format = (string)GRAY8, " \ + "width = 400, " \ + "height = 400, " \ + "framerate = 0/1" + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (SHAPEWIPE_VIDEO_CAPS_STRING) + ); +static GstStaticPadTemplate videosrctemplate = +GST_STATIC_PAD_TEMPLATE ("videosrc", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (SHAPEWIPE_VIDEO_CAPS_STRING) + ); +static GstStaticPadTemplate masksrctemplate = +GST_STATIC_PAD_TEMPLATE ("masksrc", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (SHAPEWIPE_MASK_CAPS_STRING) + ); + + +static GstBuffer *output = NULL; + +static GstFlowReturn +on_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) +{ + g_return_val_if_fail (output == NULL, GST_FLOW_ERROR); + + output = buffer; + return GST_FLOW_OK; +} + +GST_START_TEST (test_general) +{ + GstElement *shapewipe, *videosrc, *masksrc, *sink, *bin; + GstPad *p; + GstCaps *caps; + GstBuffer *mask, *input; + guint i, j; + guint8 *data; + GstMapInfo map; + + bin = gst_bin_new ("myshapewipe"); + videosrc = gst_bin_new ("myvideosrc"); + masksrc = gst_bin_new ("mymasksrc"); + sink = gst_bin_new ("mysink"); + shapewipe = gst_element_factory_make ("shapewipe", NULL); + fail_unless (shapewipe != NULL); + gst_bin_add_many (GST_BIN (bin), videosrc, masksrc, shapewipe, sink, NULL); + + myvideosrcpad = + gst_pad_new_from_static_template (&videosrctemplate, "videosrc"); + gst_element_add_pad (videosrc, myvideosrcpad); + + mymasksrcpad = gst_pad_new_from_static_template (&masksrctemplate, "masksrc"); + gst_element_add_pad (masksrc, mymasksrcpad); + + mysinkpad = gst_pad_new_from_static_template (&sinktemplate, "sink"); + gst_element_add_pad (sink, mysinkpad); + gst_pad_set_chain_function (mysinkpad, on_chain); + + p = gst_element_get_static_pad (shapewipe, "video_sink"); + fail_unless (gst_pad_link (myvideosrcpad, p) == GST_PAD_LINK_OK); + gst_object_unref (p); + p = gst_element_get_static_pad (shapewipe, "mask_sink"); + fail_unless (gst_pad_link (mymasksrcpad, p) == GST_PAD_LINK_OK); + gst_object_unref (p); + p = gst_element_get_static_pad (shapewipe, "src"); + fail_unless (gst_pad_link (p, mysinkpad) == GST_PAD_LINK_OK); + gst_object_unref (p); + + fail_unless (gst_element_set_state (bin, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS); + + caps = gst_caps_from_string (SHAPEWIPE_MASK_CAPS_STRING); + gst_check_setup_events (mymasksrcpad, masksrc, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + + caps = gst_caps_from_string (SHAPEWIPE_VIDEO_CAPS_STRING); + gst_check_setup_events (myvideosrcpad, videosrc, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + + mask = gst_buffer_new_and_alloc (400 * 400); + gst_buffer_map (mask, &map, GST_MAP_WRITE); + data = map.data; + for (i = 0; i < 400; i++) { + for (j = 0; j < 400; j++) { + if (i < 100 && j < 100) + data[0] = 0; + else if (i < 200 && j < 200) + data[0] = 85; + else if (i < 300 && j < 300) + data[0] = 170; + else + data[0] = 254; + data++; + } + } + gst_buffer_unmap (mask, &map); + + fail_unless (gst_pad_push (mymasksrcpad, mask) == GST_FLOW_OK); + + input = gst_buffer_new_and_alloc (400 * 400 * 4); + gst_buffer_map (input, &map, GST_MAP_WRITE); + data = map.data; + for (i = 0; i < 400; i++) { + for (j = 0; j < 400; j++) { + /* This is green */ + data[0] = 255; /* A */ + data[1] = 173; /* Y */ + data[2] = 42; /* U */ + data[3] = 26; /* V */ + data += 4; + } + } + gst_buffer_unmap (input, &map); + + g_object_set (G_OBJECT (shapewipe), "position", 0.0, NULL); + output = NULL; + fail_unless (gst_pad_push (myvideosrcpad, + gst_buffer_ref (input)) == GST_FLOW_OK); + fail_unless (output != NULL); + gst_buffer_map (output, &map, GST_MAP_WRITE); + data = map.data; + for (i = 0; i < 400; i++) { + for (j = 0; j < 400; j++) { + fail_unless_equals_int (data[0], 255); /* A */ + fail_unless_equals_int (data[1], 173); /* Y */ + fail_unless_equals_int (data[2], 42); /* U */ + fail_unless_equals_int (data[3], 26); /* V */ + data += 4; + } + } + gst_buffer_unmap (output, &map); + gst_buffer_unref (output); + output = NULL; + + g_object_set (G_OBJECT (shapewipe), "position", 0.1, NULL); + output = NULL; + fail_unless (gst_pad_push (myvideosrcpad, + gst_buffer_ref (input)) == GST_FLOW_OK); + fail_unless (output != NULL); + gst_buffer_map (output, &map, GST_MAP_READ); + data = map.data; + for (i = 0; i < 400; i++) { + for (j = 0; j < 400; j++) { + if (i < 100 && j < 100) { + fail_unless_equals_int (data[0], 0); /* A */ + fail_unless_equals_int (data[1], 173); /* Y */ + fail_unless_equals_int (data[2], 42); /* U */ + fail_unless_equals_int (data[3], 26); /* V */ + } else { + fail_unless_equals_int (data[0], 255); /* A */ + fail_unless_equals_int (data[1], 173); /* Y */ + fail_unless_equals_int (data[2], 42); /* U */ + fail_unless_equals_int (data[3], 26); /* V */ + } + data += 4; + } + } + gst_buffer_unmap (output, &map); + gst_buffer_unref (output); + output = NULL; + + g_object_set (G_OBJECT (shapewipe), "position", 0.34, NULL); + output = NULL; + fail_unless (gst_pad_push (myvideosrcpad, + gst_buffer_ref (input)) == GST_FLOW_OK); + fail_unless (output != NULL); + gst_buffer_map (output, &map, GST_MAP_READ); + data = map.data; + for (i = 0; i < 400; i++) { + for (j = 0; j < 400; j++) { + if (i < 200 && j < 200) { + fail_unless_equals_int (data[0], 0); /* A */ + fail_unless_equals_int (data[1], 173); /* Y */ + fail_unless_equals_int (data[2], 42); /* U */ + fail_unless_equals_int (data[3], 26); /* V */ + } else { + fail_unless_equals_int (data[0], 255); /* A */ + fail_unless_equals_int (data[1], 173); /* Y */ + fail_unless_equals_int (data[2], 42); /* U */ + fail_unless_equals_int (data[3], 26); /* V */ + } + data += 4; + } + } + gst_buffer_unmap (output, &map); + gst_buffer_unref (output); + output = NULL; + + g_object_set (G_OBJECT (shapewipe), "position", 0.67, NULL); + output = NULL; + fail_unless (gst_pad_push (myvideosrcpad, + gst_buffer_ref (input)) == GST_FLOW_OK); + fail_unless (output != NULL); + gst_buffer_map (output, &map, GST_MAP_READ); + data = map.data; + for (i = 0; i < 400; i++) { + for (j = 0; j < 400; j++) { + if (i < 300 && j < 300) { + fail_unless_equals_int (data[0], 0); /* A */ + fail_unless_equals_int (data[1], 173); /* Y */ + fail_unless_equals_int (data[2], 42); /* U */ + fail_unless_equals_int (data[3], 26); /* V */ + } else { + fail_unless_equals_int (data[0], 255); /* A */ + fail_unless_equals_int (data[1], 173); /* Y */ + fail_unless_equals_int (data[2], 42); /* U */ + fail_unless_equals_int (data[3], 26); /* V */ + } + data += 4; + } + } + gst_buffer_unmap (output, &map); + gst_buffer_unref (output); + output = NULL; + + g_object_set (G_OBJECT (shapewipe), "position", 1.0, NULL); + output = NULL; + fail_unless (gst_pad_push (myvideosrcpad, + gst_buffer_ref (input)) == GST_FLOW_OK); + fail_unless (output != NULL); + gst_buffer_map (output, &map, GST_MAP_READ); + data = map.data; + for (i = 0; i < 400; i++) { + for (j = 0; j < 400; j++) { + fail_unless_equals_int (data[0], 0); /* A */ + fail_unless_equals_int (data[1], 173); /* Y */ + fail_unless_equals_int (data[2], 42); /* U */ + fail_unless_equals_int (data[3], 26); /* V */ + data += 4; + } + } + gst_buffer_unmap (output, &map); + gst_buffer_unref (output); + output = NULL; + + gst_buffer_unref (input); + + fail_unless (gst_element_set_state (bin, + GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS); + + p = gst_element_get_static_pad (shapewipe, "video_sink"); + fail_unless (gst_pad_unlink (myvideosrcpad, p)); + gst_object_unref (p); + p = gst_element_get_static_pad (shapewipe, "mask_sink"); + fail_unless (gst_pad_unlink (mymasksrcpad, p)); + gst_object_unref (p); + p = gst_element_get_static_pad (shapewipe, "src"); + fail_unless (gst_pad_unlink (p, mysinkpad)); + gst_object_unref (p); + + gst_object_unref (bin); +} + +GST_END_TEST; + +static Suite * +shapewipe_suite (void) +{ + Suite *s = suite_create ("shapewipe"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_set_timeout (tc_chain, 180); + tcase_add_test (tc_chain, test_general); + + return s; +} + +GST_CHECK_MAIN (shapewipe); diff --git a/tests/check/elements/souphttpsrc.c b/tests/check/elements/souphttpsrc.c new file mode 100755 index 0000000..dfb682b --- /dev/null +++ b/tests/check/elements/souphttpsrc.c @@ -0,0 +1,700 @@ +/* GStreamer unit tests for the souphttpsrc element + * Copyright (C) 2006-2007 Tim-Philipp Müller <tim centricular net> + * Copyright (C) 2008 Wouter Cloetens <wouter@mind.be> + * Copyright (C) 2001-2003, Ximian, Inc. + * + * 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 + +#include <stdlib.h> + +#include <glib.h> +#include <glib/gprintf.h> + +#define SOUP_VERSION_MIN_REQUIRED (SOUP_VERSION_2_40) +#include <libsoup/soup.h> +#include <gst/check/gstcheck.h> + +#if !defined(SOUP_MINOR_VERSION) || SOUP_MINOR_VERSION < 44 +#define SoupStatus SoupKnownStatusCode +#endif + +static guint http_port = 0, https_port = 0; + +gboolean redirect = TRUE; + +static const char **cookies = NULL; + +/* Variables for authentication tests */ +static const char *user_id = NULL; +static const char *user_pw = NULL; +static const char *good_user = "good_user"; +static const char *bad_user = "bad_user"; +static const char *good_pw = "good_pw"; +static const char *bad_pw = "bad_pw"; +static const char *realm = "SOUPHTTPSRC_REALM"; +static const char *basic_auth_path = "/basic_auth"; +static const char *digest_auth_path = "/digest_auth"; + +static gboolean run_server (guint * http_port, guint * https_port); +static void stop_server (void); + +static void +handoff_cb (GstElement * fakesink, GstBuffer * buf, GstPad * pad, + GstBuffer ** p_outbuf) +{ + GST_LOG ("handoff, buf = %p", buf); + if (*p_outbuf == NULL) + *p_outbuf = gst_buffer_ref (buf); +} + +static gboolean +basic_auth_cb (SoupAuthDomain * domain, SoupMessage * msg, + const char *username, const char *password, gpointer user_data) +{ + /* There is only one good login for testing */ + return (strcmp (username, good_user) == 0) + && (strcmp (password, good_pw) == 0); +} + + +static char * +digest_auth_cb (SoupAuthDomain * domain, SoupMessage * msg, + const char *username, gpointer user_data) +{ + /* There is only one good login for testing */ + if (strcmp (username, good_user) == 0) + return soup_auth_domain_digest_encode_password (good_user, realm, good_pw); + return NULL; +} + +static int +run_test (const char *format, ...) +{ + GstStateChangeReturn ret; + + GstElement *pipe, *src, *sink; + + GstBuffer *buf = NULL; + + GstMessage *msg; + + gchar *url; + + va_list args; + + int rc = -1; + + pipe = gst_pipeline_new (NULL); + + src = gst_element_factory_make ("souphttpsrc", NULL); + fail_unless (src != NULL); + + sink = gst_element_factory_make ("fakesink", NULL); + fail_unless (sink != NULL); + + gst_bin_add (GST_BIN (pipe), src); + gst_bin_add (GST_BIN (pipe), sink); + fail_unless (gst_element_link (src, sink)); + + if (http_port == 0) { + GST_DEBUG ("failed to start soup http server"); + } + fail_unless (http_port != 0); + va_start (args, format); + g_vasprintf (&url, format, args); + va_end (args); + fail_unless (url != NULL); + g_object_set (src, "location", url, NULL); + g_free (url); + + g_object_set (src, "automatic-redirect", redirect, NULL); + if (cookies != NULL) + g_object_set (src, "cookies", cookies, NULL); + g_object_set (sink, "signal-handoffs", TRUE, NULL); + g_signal_connect (sink, "preroll-handoff", G_CALLBACK (handoff_cb), &buf); + + if (user_id != NULL) + g_object_set (src, "user-id", user_id, NULL); + if (user_pw != NULL) + g_object_set (src, "user-pw", user_pw, NULL); + + ret = gst_element_set_state (pipe, GST_STATE_PAUSED); + if (ret != GST_STATE_CHANGE_ASYNC) { + GST_DEBUG ("failed to start up soup http src, ret = %d", ret); + goto done; + } + + gst_element_set_state (pipe, GST_STATE_PLAYING); + msg = gst_bus_poll (GST_ELEMENT_BUS (pipe), + GST_MESSAGE_EOS | GST_MESSAGE_ERROR, -1); + if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) { + gchar *debug = NULL; + + GError *err = NULL; + + gst_message_parse_error (msg, &err, &debug); + GST_INFO ("error: %s", err->message); + if (g_str_has_suffix (err->message, "Not Found")) + rc = 404; + else if (g_str_has_suffix (err->message, "Forbidden")) + rc = 403; + else if (g_str_has_suffix (err->message, "Unauthorized")) + rc = 401; + else if (g_str_has_suffix (err->message, "Found")) + rc = 302; + GST_INFO ("debug: %s", debug); + /* should not've gotten any output in case of a 40x error. Wait a bit + * to give streaming thread a chance to push out a buffer and triggering + * our callback before shutting down the pipeline */ + g_usleep (G_USEC_PER_SEC / 2); + fail_unless (buf == NULL); + g_error_free (err); + g_free (debug); + gst_message_unref (msg); + goto done; + } + gst_message_unref (msg); + + /* don't wait for more than 10 seconds */ + ret = gst_element_get_state (pipe, NULL, NULL, 10 * GST_SECOND); + GST_LOG ("ret = %u", ret); + + if (buf == NULL) { + /* we want to test the buffer offset, nothing else; if there's a failure + * it might be for lots of reasons (no network connection, whatever), we're + * not interested in those */ + GST_DEBUG ("didn't manage to get data within 10 seconds, skipping test"); + goto done; + } + + GST_DEBUG ("buffer offset = %" G_GUINT64_FORMAT, GST_BUFFER_OFFSET (buf)); + + /* first buffer should have a 0 offset */ + fail_unless (GST_BUFFER_OFFSET (buf) == 0); + gst_buffer_unref (buf); + rc = 0; + +done: + + gst_element_set_state (pipe, GST_STATE_NULL); + gst_object_unref (pipe); + return rc; +} + +GST_START_TEST (test_first_buffer_has_offset) +{ + fail_unless (run_test ("http://127.0.0.1:%u/", http_port) == 0); +} + +GST_END_TEST; + +GST_START_TEST (test_not_found) +{ + fail_unless (run_test ("http://127.0.0.1:%u/404", http_port) == 404); + fail_unless (run_test ("http://127.0.0.1:%u/404-with-data", + http_port) == 404); +} + +GST_END_TEST; + +GST_START_TEST (test_forbidden) +{ + fail_unless (run_test ("http://127.0.0.1:%u/403", http_port) == 403); +} + +GST_END_TEST; + +GST_START_TEST (test_redirect_no) +{ + redirect = FALSE; + fail_unless (run_test ("http://127.0.0.1:%u/302", http_port) == 302); +} + +GST_END_TEST; + +GST_START_TEST (test_redirect_yes) +{ + redirect = TRUE; + fail_unless (run_test ("http://127.0.0.1:%u/302", http_port) == 0); +} + +GST_END_TEST; + +GST_START_TEST (test_https) +{ + if (!https_port) + GST_INFO ("Failed to start an HTTPS server; let's just skip this test."); + else + fail_unless (run_test ("https://127.0.0.1:%u/", https_port) == 0); +} + +GST_END_TEST; + +GST_START_TEST (test_cookies) +{ + static const char *biscotti[] = { "delacre=yummie", "koekje=lu", NULL }; + int rc; + + cookies = biscotti; + rc = run_test ("http://127.0.0.1:%u/", http_port); + cookies = NULL; + fail_unless (rc == 0); +} + +GST_END_TEST; + +GST_START_TEST (test_good_user_basic_auth) +{ + int res; + + user_id = good_user; + user_pw = good_pw; + res = run_test ("http://127.0.0.1:%u%s", http_port, basic_auth_path); + GST_DEBUG ("Basic Auth user %s password %s res = %d", user_id, user_pw, res); + user_id = user_pw = NULL; + fail_unless (res == 0); +} + +GST_END_TEST; + +GST_START_TEST (test_bad_user_basic_auth) +{ + int res; + + user_id = bad_user; + user_pw = good_pw; + res = run_test ("http://127.0.0.1:%u%s", http_port, basic_auth_path); + GST_DEBUG ("Basic Auth user %s password %s res = %d", user_id, user_pw, res); + user_id = user_pw = NULL; + fail_unless (res == 401); +} + +GST_END_TEST; + +GST_START_TEST (test_bad_password_basic_auth) +{ + int res; + + user_id = good_user; + user_pw = bad_pw; + res = run_test ("http://127.0.0.1:%u%s", http_port, basic_auth_path); + GST_DEBUG ("Basic Auth user %s password %s res = %d", user_id, user_pw, res); + user_id = user_pw = NULL; + fail_unless (res == 401); +} + +GST_END_TEST; + +GST_START_TEST (test_good_user_digest_auth) +{ + int res; + + user_id = good_user; + user_pw = good_pw; + res = run_test ("http://127.0.0.1:%u%s", http_port, digest_auth_path); + GST_DEBUG ("Digest Auth user %s password %s res = %d", user_id, user_pw, res); + user_id = user_pw = NULL; + fail_unless (res == 0); +} + +GST_END_TEST; + +GST_START_TEST (test_bad_user_digest_auth) +{ + int res; + + user_id = bad_user; + user_pw = good_pw; + res = run_test ("http://127.0.0.1:%u%s", http_port, digest_auth_path); + GST_DEBUG ("Digest Auth user %s password %s res = %d", user_id, user_pw, res); + user_id = user_pw = NULL; + fail_unless (res == 401); +} + +GST_END_TEST; + +GST_START_TEST (test_bad_password_digest_auth) +{ + int res; + + user_id = good_user; + user_pw = bad_pw; + res = run_test ("http://127.0.0.1:%u%s", http_port, digest_auth_path); + GST_DEBUG ("Digest Auth user %s password %s res = %d", user_id, user_pw, res); + user_id = user_pw = NULL; + fail_unless (res == 401); +} + +GST_END_TEST; + +static gboolean icy_caps = FALSE; + +static void +got_buffer (GstElement * fakesink, GstBuffer * buf, GstPad * pad, + gpointer user_data) +{ + GstStructure *s; + GstCaps *caps; + + /* Caps can be anything if we don't except icy caps */ + if (!icy_caps) + return; + + /* Otherwise they _must_ be "application/x-icy" */ + caps = gst_pad_get_current_caps (pad); + fail_unless (caps != NULL); + s = gst_caps_get_structure (caps, 0); + fail_unless_equals_string (gst_structure_get_name (s), "application/x-icy"); + gst_caps_unref (caps); +} + +GST_START_TEST (test_icy_stream) +{ + GstElement *pipe, *src, *sink; + + GstMessage *msg; + + pipe = gst_pipeline_new (NULL); + + src = gst_element_factory_make ("souphttpsrc", NULL); + fail_unless (src != NULL); + + sink = gst_element_factory_make ("fakesink", NULL); + fail_unless (sink != NULL); + g_object_set (sink, "signal-handoffs", TRUE, NULL); + g_signal_connect (sink, "handoff", G_CALLBACK (got_buffer), NULL); + + gst_bin_add (GST_BIN (pipe), src); + gst_bin_add (GST_BIN (pipe), sink); + fail_unless (gst_element_link (src, sink)); + + /* First try Virgin Radio Ogg stream, to see if there's connectivity and all + * (which is an attempt to work around the completely horrid error reporting + * and that we can't distinguish different types of failures here). */ + + g_object_set (src, "location", "http://ogg2.smgradio.com/vr32.ogg", NULL); + g_object_set (src, "num-buffers", 1, NULL); + icy_caps = FALSE; + gst_element_set_state (pipe, GST_STATE_PLAYING); + + msg = gst_bus_poll (GST_ELEMENT_BUS (pipe), + GST_MESSAGE_EOS | GST_MESSAGE_ERROR, -1); + if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) { + GST_INFO ("looks like there's no net connectivity or sgmradio.com is " + "down. In any case, let's just skip this test"); + gst_message_unref (msg); + goto done; + } + gst_message_unref (msg); + msg = NULL; + gst_element_set_state (pipe, GST_STATE_NULL); + + /* Now, if the ogg stream works, the mp3 shoutcast stream should work as + * well (time will tell if that's true) */ + + /* Virgin Radio 32kbps mp3 shoutcast stream */ + g_object_set (src, "location", "http://mp3-vr-32.smgradio.com:80/", NULL); + + + /* EOS after the first buffer */ + g_object_set (src, "num-buffers", 1, NULL); + icy_caps = TRUE; + gst_element_set_state (pipe, GST_STATE_PLAYING); + msg = gst_bus_poll (GST_ELEMENT_BUS (pipe), + GST_MESSAGE_EOS | GST_MESSAGE_ERROR, -1); + + if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_EOS) { + GST_DEBUG ("success, we're done here"); + gst_message_unref (msg); + goto done; + } + + { + GError *err = NULL; + + gst_message_parse_error (msg, &err, NULL); + gst_message_unref (msg); + g_error ("Error with ICY mp3 shoutcast stream: %s", err->message); + g_error_free (err); + } + +done: + icy_caps = FALSE; + + gst_element_set_state (pipe, GST_STATE_NULL); + gst_object_unref (pipe); +} + +GST_END_TEST; + +static SoupServer *server; /* NULL */ +static SoupServer *ssl_server; /* NULL */ + +static Suite * +souphttpsrc_suite (void) +{ + TCase *tc_chain, *tc_internet; + Suite *s; + + /* we don't support exceptions from the proxy, so just unset the environment + * variable - in case it's set in the test environment it would otherwise + * prevent us from connecting to localhost (like jenkins.qa.ubuntu.com) */ + g_unsetenv ("http_proxy"); + + s = suite_create ("souphttpsrc"); + tc_chain = tcase_create ("general"); + tc_internet = tcase_create ("internet"); + + suite_add_tcase (s, tc_chain); + if (run_server (&http_port, &https_port)) { + atexit (stop_server); + tcase_add_test (tc_chain, test_first_buffer_has_offset); + tcase_add_test (tc_chain, test_redirect_yes); + tcase_add_test (tc_chain, test_redirect_no); + tcase_add_test (tc_chain, test_not_found); + tcase_add_test (tc_chain, test_forbidden); + tcase_add_test (tc_chain, test_cookies); + tcase_add_test (tc_chain, test_good_user_basic_auth); + tcase_add_test (tc_chain, test_bad_user_basic_auth); + tcase_add_test (tc_chain, test_bad_password_basic_auth); + tcase_add_test (tc_chain, test_good_user_digest_auth); + tcase_add_test (tc_chain, test_bad_user_digest_auth); + tcase_add_test (tc_chain, test_bad_password_digest_auth); + + if (ssl_server != NULL) + tcase_add_test (tc_chain, test_https); + } else { + g_print ("Skipping 12 souphttpsrc tests, couldn't start or connect to " + "local http server\n"); + } + + suite_add_tcase (s, tc_internet); + tcase_set_timeout (tc_internet, 250); + tcase_add_test (tc_internet, test_icy_stream); + + return s; +} + +GST_CHECK_MAIN (souphttpsrc); + +static void +do_get (SoupMessage * msg, const char *path) +{ + gboolean send_error_doc = FALSE; + char *uri; + + int buflen = 4096; + + SoupStatus status = SOUP_STATUS_OK; + + uri = soup_uri_to_string (soup_message_get_uri (msg), FALSE); + GST_DEBUG ("request: \"%s\"", uri); + + if (!strcmp (path, "/301")) + status = SOUP_STATUS_MOVED_PERMANENTLY; + else if (!strcmp (path, "/302")) + status = SOUP_STATUS_MOVED_TEMPORARILY; + else if (!strcmp (path, "/307")) + status = SOUP_STATUS_TEMPORARY_REDIRECT; + else if (!strcmp (path, "/403")) + status = SOUP_STATUS_FORBIDDEN; + else if (!strcmp (path, "/404")) + status = SOUP_STATUS_NOT_FOUND; + else if (!strcmp (path, "/404-with-data")) { + status = SOUP_STATUS_NOT_FOUND; + send_error_doc = TRUE; + } + + if (SOUP_STATUS_IS_REDIRECTION (status)) { + char *redir_uri; + + redir_uri = g_strdup_printf ("%s-redirected", uri); + soup_message_headers_append (msg->response_headers, "Location", redir_uri); + g_free (redir_uri); + } + if (status != (SoupStatus) SOUP_STATUS_OK && !send_error_doc) + goto leave; + + if (msg->method == SOUP_METHOD_GET) { + char *buf; + + buf = g_malloc (buflen); + memset (buf, 0, buflen); + soup_message_body_append (msg->response_body, SOUP_MEMORY_TAKE, + buf, buflen); + } else { /* msg->method == SOUP_METHOD_HEAD */ + + char *length; + + /* We could just use the same code for both GET and + * HEAD. But we'll optimize and avoid the extra + * malloc. + */ + length = g_strdup_printf ("%lu", (gulong) buflen); + soup_message_headers_append (msg->response_headers, + "Content-Length", length); + g_free (length); + } + +leave: + soup_message_set_status (msg, status); + g_free (uri); +} + +static void +print_header (const char *name, const char *value, gpointer data) +{ + GST_DEBUG ("header: %s: %s", name, value); +} + +static void +server_callback (SoupServer * server, SoupMessage * msg, + const char *path, GHashTable * query, + SoupClientContext * context, gpointer data) +{ + GST_DEBUG ("%s %s HTTP/1.%d", msg->method, path, + soup_message_get_http_version (msg)); + soup_message_headers_foreach (msg->request_headers, print_header, NULL); + if (msg->request_body->length) + GST_DEBUG ("%s", msg->request_body->data); + + if (msg->method == SOUP_METHOD_GET || msg->method == SOUP_METHOD_HEAD) + do_get (msg, path); + else + soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED); + + GST_DEBUG (" -> %d %s", msg->status_code, msg->reason_phrase); +} + +static gboolean +run_server (guint * http_port, guint * https_port) +{ + guint port = SOUP_ADDRESS_ANY_PORT; + guint ssl_port = SOUP_ADDRESS_ANY_PORT; + const char *ssl_cert_file = GST_TEST_FILES_PATH "/test-cert.pem"; + const char *ssl_key_file = GST_TEST_FILES_PATH "/test-key.pem"; + static int server_running = 0; + + SoupAuthDomain *domain = NULL; + + if (server_running) + return TRUE; + + server_running = 1; + + *http_port = *https_port = 0; + + /* The G_ENABLE_DIAGNOSTIC is temporarily overriden to avoid + * property deprecation warnings (for the SOUP_SERVER_PORT + * property) */ + g_setenv ("G_ENABLE_DIAGNOSTIC", "0", TRUE); + server = soup_server_new (SOUP_SERVER_PORT, port, NULL); + g_setenv ("G_ENABLE_DIAGNOSTIC", "1", TRUE); + if (!server) { + GST_DEBUG ("Unable to bind to server port %u", port); + return FALSE; + } + *http_port = soup_server_get_port (server); + GST_INFO ("HTTP server listening on port %u", *http_port); + soup_server_add_handler (server, NULL, server_callback, NULL, NULL); + domain = soup_auth_domain_basic_new (SOUP_AUTH_DOMAIN_REALM, realm, + SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, basic_auth_cb, + SOUP_AUTH_DOMAIN_ADD_PATH, basic_auth_path, NULL); + soup_server_add_auth_domain (server, domain); + g_object_unref (domain); + domain = soup_auth_domain_digest_new (SOUP_AUTH_DOMAIN_REALM, realm, + SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK, digest_auth_cb, + SOUP_AUTH_DOMAIN_ADD_PATH, digest_auth_path, NULL); + soup_server_add_auth_domain (server, domain); + g_object_unref (domain); + soup_server_run_async (server); + + if (ssl_cert_file && ssl_key_file) { + GTlsBackend *backend = g_tls_backend_get_default (); + + if (backend != NULL && g_tls_backend_supports_tls (backend)) { + ssl_server = soup_server_new (SOUP_SERVER_PORT, ssl_port, + SOUP_SERVER_SSL_CERT_FILE, ssl_cert_file, + SOUP_SERVER_SSL_KEY_FILE, ssl_key_file, NULL); + } else { + GST_INFO ("No TLS support"); + } + + if (ssl_server) { + *https_port = soup_server_get_port (ssl_server); + GST_INFO ("HTTPS server listening on port %u", *https_port); + soup_server_add_handler (ssl_server, NULL, server_callback, NULL, NULL); + soup_server_run_async (ssl_server); + } + } + + /* check if we can connect to our local http server */ + { + GSocketConnection *conn; + GSocketClient *client; + + client = g_socket_client_new (); + g_socket_client_set_timeout (client, 2); + conn = g_socket_client_connect_to_host (client, "127.0.0.1", *http_port, + NULL, NULL); + if (conn == NULL) { + GST_INFO ("Couldn't connect to http server 127.0.0.1:%u", *http_port); + g_object_unref (client); + stop_server (); + return FALSE; + } + g_object_unref (conn); + + if (ssl_server == NULL) + goto skip_https_check; + + conn = g_socket_client_connect_to_host (client, "127.0.0.1", *https_port, + NULL, NULL); + if (conn == NULL) { + GST_INFO ("Couldn't connect to https server 127.0.0.1:%u", *https_port); + g_object_unref (client); + stop_server (); + return FALSE; + } + g_object_unref (conn); + + skip_https_check: + + g_object_unref (client); + } + + return TRUE; +} + +static void +stop_server (void) +{ + GST_INFO ("cleaning up"); + + if (server) { + g_object_unref (server); + server = NULL; + } + if (ssl_server) { + g_object_unref (ssl_server); + ssl_server = NULL; + } +} diff --git a/tests/check/elements/spectrum.c b/tests/check/elements/spectrum.c new file mode 100644 index 0000000..e562e72 --- /dev/null +++ b/tests/check/elements/spectrum.c @@ -0,0 +1,556 @@ +/* GStreamer + * + * unit test for spectrum + * + * Copyright (C) <2007> Stefan Kost <ensonic@users.sf.net> + * + * 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 <unistd.h> + +#include <gst/audio/audio.h> +#include <gst/check/gstcheck.h> + +gboolean have_eos = FALSE; + +/* 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 */ +GstPad *mysrcpad, *mysinkpad; + +#define SPECT_CAPS_TEMPLATE_STRING \ + "audio/x-raw, " \ + " rate = (int) [ 1, MAX ], " \ + " channels = (int) [ 1, MAX ], " \ + " layout = (string) interleaved, " \ + " format = (string) { " \ + GST_AUDIO_NE(S16) ", " \ + GST_AUDIO_NE(S32) ", " \ + GST_AUDIO_NE(F32) ", " \ + GST_AUDIO_NE(F64) " }" + +#define SPECT_CAPS_STRING_S16 \ + "audio/x-raw, " \ + "rate = (int) 44100, " \ + "channels = (int) 1, " \ + "layout = (string) interleaved, " \ + "format = (string) " GST_AUDIO_NE(S16) + +#define SPECT_CAPS_STRING_S32 \ + "audio/x-raw, " \ + "rate = (int) 44100, " \ + "channels = (int) 1, " \ + "layout = (string) interleaved, " \ + "format = (string) " GST_AUDIO_NE(S32) + +#define SPECT_CAPS_STRING_F32 \ + "audio/x-raw, " \ + " rate = (int) 44100, " \ + " channels = (int) 1, " \ + " layout = (string) interleaved, " \ + " format = (string) " GST_AUDIO_NE(F32) + +#define SPECT_CAPS_STRING_F64 \ + "audio/x-raw, " \ + " rate = (int) 44100, " \ + " channels = (int) 1, " \ + " layout = (string) interleaved, " \ + " format = (string) " GST_AUDIO_NE(F64) + +#define SPECT_BANDS 256 + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (SPECT_CAPS_TEMPLATE_STRING) + ); +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (SPECT_CAPS_TEMPLATE_STRING) + ); + +/* takes over reference for outcaps */ +static GstElement * +setup_spectrum (const gchar * caps_str) +{ + GstElement *spectrum; + GstCaps *caps; + + GST_DEBUG ("setup_spectrum"); + spectrum = gst_check_setup_element ("spectrum"); + mysrcpad = gst_check_setup_src_pad (spectrum, &srctemplate); + mysinkpad = gst_check_setup_sink_pad (spectrum, &sinktemplate); + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + + caps = gst_caps_from_string (caps_str); + gst_check_setup_events (mysrcpad, spectrum, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + + return spectrum; +} + +static void +cleanup_spectrum (GstElement * spectrum) +{ + GST_DEBUG ("cleanup_spectrum"); + + gst_pad_set_active (mysrcpad, FALSE); + gst_pad_set_active (mysinkpad, FALSE); + gst_check_teardown_src_pad (spectrum); + gst_check_teardown_sink_pad (spectrum); + gst_check_teardown_element (spectrum); + + g_list_foreach (buffers, (GFunc) gst_mini_object_unref, NULL); + g_list_free (buffers); + buffers = NULL; +} + + +GST_START_TEST (test_int16) +{ + GstElement *spectrum; + GstBuffer *inbuffer, *outbuffer; + GstBus *bus; + GstMessage *message; + const GstStructure *structure; + int i, j; + gint16 *data; + GstMapInfo map; + const GValue *list, *value; + GstClockTime endtime; + gfloat level; + + spectrum = setup_spectrum (SPECT_CAPS_STRING_S16); + g_object_set (spectrum, "post-messages", TRUE, "interval", GST_SECOND / 100, + "bands", SPECT_BANDS, "threshold", -80, NULL); + + fail_unless (gst_element_set_state (spectrum, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + /* create a 1 sec buffer with an 11025 Hz sine wave */ + inbuffer = gst_buffer_new_allocate (NULL, 44100 * sizeof (gint16), 0); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + data = (gint16 *) map.data; + for (j = 0; j < 44100; j += 4) { + *data = 0; + ++data; + *data = 32767; + ++data; + *data = 0; + ++data; + *data = -32767; + ++data; + } + gst_buffer_unmap (inbuffer, &map); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* create a bus to get the spectrum message on */ + bus = gst_bus_new (); + ASSERT_OBJECT_REFCOUNT (bus, "bus", 1); + gst_element_set_bus (spectrum, bus); + ASSERT_OBJECT_REFCOUNT (bus, "bus", 2); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being collected on the global buffer list */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + + message = gst_bus_poll (bus, GST_MESSAGE_ELEMENT, -1); + ASSERT_OBJECT_REFCOUNT (message, "message", 1); + + fail_unless (message != NULL); + fail_unless (GST_MESSAGE_SRC (message) == GST_OBJECT (spectrum)); + fail_unless (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ELEMENT); + structure = gst_message_get_structure (message); + fail_if (structure == NULL); + fail_unless_equals_string ((char *) gst_structure_get_name (structure), + "spectrum"); + fail_unless (gst_structure_get_clock_time (structure, "endtime", &endtime)); + + list = gst_structure_get_value (structure, "magnitude"); + for (i = 0; i < SPECT_BANDS; ++i) { + value = gst_value_list_get_value (list, i); + level = g_value_get_float (value); + GST_DEBUG ("band[%3d] is %.2f", i, level); + /* Only the bands in the middle should have a level above 60 */ + fail_if ((i == SPECT_BANDS / 2 || i == SPECT_BANDS / 2 - 1) + && level < -20.0); + fail_if ((i != SPECT_BANDS / 2 && i != SPECT_BANDS / 2 - 1) + && level > -20.0); + } + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + + /* clean up */ + /* flush current messages,and future state change messages */ + gst_bus_set_flushing (bus, TRUE); + + /* message has a ref to the element */ + ASSERT_OBJECT_REFCOUNT (spectrum, "spectrum", 2); + gst_message_unref (message); + ASSERT_OBJECT_REFCOUNT (spectrum, "spectrum", 1); + + gst_element_set_bus (spectrum, NULL); + ASSERT_OBJECT_REFCOUNT (bus, "bus", 1); + gst_object_unref (bus); + fail_unless (gst_element_set_state (spectrum, + GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS, "could not set to null"); + ASSERT_OBJECT_REFCOUNT (spectrum, "spectrum", 1); + cleanup_spectrum (spectrum); +} + +GST_END_TEST; + +GST_START_TEST (test_int32) +{ + GstElement *spectrum; + GstBuffer *inbuffer, *outbuffer; + GstBus *bus; + GstMessage *message; + const GstStructure *structure; + int i, j; + gint32 *data; + GstMapInfo map; + const GValue *list, *value; + GstClockTime endtime; + gfloat level; + + spectrum = setup_spectrum (SPECT_CAPS_STRING_S32); + g_object_set (spectrum, "post-messages", TRUE, "interval", GST_SECOND / 100, + "bands", SPECT_BANDS, "threshold", -80, NULL); + + fail_unless (gst_element_set_state (spectrum, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + /* create a 1 sec buffer with an 11025 Hz sine wave */ + inbuffer = gst_buffer_new_allocate (NULL, 44100 * sizeof (gint32), 0); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + data = (gint32 *) map.data; + for (j = 0; j < 44100; j += 4) { + *data = 0; + ++data; + *data = 2147483647; + ++data; + *data = 0; + ++data; + *data = -2147483647; + ++data; + } + gst_buffer_unmap (inbuffer, &map); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* create a bus to get the spectrum message on */ + bus = gst_bus_new (); + ASSERT_OBJECT_REFCOUNT (bus, "bus", 1); + gst_element_set_bus (spectrum, bus); + ASSERT_OBJECT_REFCOUNT (bus, "bus", 2); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being collected on the global buffer list */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + + message = gst_bus_poll (bus, GST_MESSAGE_ELEMENT, -1); + ASSERT_OBJECT_REFCOUNT (message, "message", 1); + + fail_unless (message != NULL); + fail_unless (GST_MESSAGE_SRC (message) == GST_OBJECT (spectrum)); + fail_unless (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ELEMENT); + structure = gst_message_get_structure (message); + fail_if (structure == NULL); + fail_unless_equals_string ((char *) gst_structure_get_name (structure), + "spectrum"); + fail_unless (gst_structure_get_clock_time (structure, "endtime", &endtime)); + + list = gst_structure_get_value (structure, "magnitude"); + for (i = 0; i < SPECT_BANDS; ++i) { + value = gst_value_list_get_value (list, i); + level = g_value_get_float (value); + GST_DEBUG ("band[%3d] is %.2f", i, level); + /* Only the bands in the middle should have a level above 60 */ + fail_if ((i == SPECT_BANDS / 2 || i == SPECT_BANDS / 2 - 1) + && level < -20.0); + fail_if ((i != SPECT_BANDS / 2 && i != SPECT_BANDS / 2 - 1) + && level > -20.0); + } + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + + /* clean up */ + /* flush current messages,and future state change messages */ + gst_bus_set_flushing (bus, TRUE); + + /* message has a ref to the element */ + ASSERT_OBJECT_REFCOUNT (spectrum, "spectrum", 2); + gst_message_unref (message); + ASSERT_OBJECT_REFCOUNT (spectrum, "spectrum", 1); + + gst_element_set_bus (spectrum, NULL); + ASSERT_OBJECT_REFCOUNT (bus, "bus", 1); + gst_object_unref (bus); + fail_unless (gst_element_set_state (spectrum, + GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS, "could not set to null"); + ASSERT_OBJECT_REFCOUNT (spectrum, "spectrum", 1); + cleanup_spectrum (spectrum); +} + +GST_END_TEST; + +GST_START_TEST (test_float32) +{ + GstElement *spectrum; + GstBuffer *inbuffer, *outbuffer; + GstBus *bus; + GstMessage *message; + const GstStructure *structure; + int i, j; + gfloat *data; + GstMapInfo map; + const GValue *list, *value; + GstClockTime endtime; + gfloat level; + + spectrum = setup_spectrum (SPECT_CAPS_STRING_F32); + g_object_set (spectrum, "post-messages", TRUE, "interval", GST_SECOND / 100, + "bands", SPECT_BANDS, "threshold", -80, NULL); + + fail_unless (gst_element_set_state (spectrum, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + /* create a 1 sec buffer with an 11025 Hz sine wave */ + inbuffer = gst_buffer_new_allocate (NULL, 44100 * sizeof (gfloat), 0); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + data = (gfloat *) map.data; + for (j = 0; j < 44100; j += 4) { + *data = 0.0; + ++data; + *data = 1.0; + ++data; + *data = 0.0; + ++data; + *data = -1.0; + ++data; + } + gst_buffer_unmap (inbuffer, &map); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* create a bus to get the spectrum message on */ + bus = gst_bus_new (); + ASSERT_OBJECT_REFCOUNT (bus, "bus", 1); + gst_element_set_bus (spectrum, bus); + ASSERT_OBJECT_REFCOUNT (bus, "bus", 2); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being collected on the global buffer list */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + + message = gst_bus_poll (bus, GST_MESSAGE_ELEMENT, -1); + ASSERT_OBJECT_REFCOUNT (message, "message", 1); + + fail_unless (message != NULL); + fail_unless (GST_MESSAGE_SRC (message) == GST_OBJECT (spectrum)); + fail_unless (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ELEMENT); + structure = gst_message_get_structure (message); + fail_if (structure == NULL); + fail_unless_equals_string ((char *) gst_structure_get_name (structure), + "spectrum"); + fail_unless (gst_structure_get_clock_time (structure, "endtime", &endtime)); + + list = gst_structure_get_value (structure, "magnitude"); + for (i = 0; i < SPECT_BANDS; ++i) { + value = gst_value_list_get_value (list, i); + level = g_value_get_float (value); + GST_DEBUG ("band[%3d] is %.2f", i, level); + /* Only the bands in the middle should have a level above 60 */ + fail_if ((i == SPECT_BANDS / 2 || i == SPECT_BANDS / 2 - 1) + && level < -20.0); + fail_if ((i != SPECT_BANDS / 2 && i != SPECT_BANDS / 2 - 1) + && level > -20.0); + } + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + + /* clean up */ + /* flush current messages,and future state change messages */ + gst_bus_set_flushing (bus, TRUE); + + /* message has a ref to the element */ + ASSERT_OBJECT_REFCOUNT (spectrum, "spectrum", 2); + gst_message_unref (message); + ASSERT_OBJECT_REFCOUNT (spectrum, "spectrum", 1); + + gst_element_set_bus (spectrum, NULL); + ASSERT_OBJECT_REFCOUNT (bus, "bus", 1); + gst_object_unref (bus); + fail_unless (gst_element_set_state (spectrum, + GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS, "could not set to null"); + ASSERT_OBJECT_REFCOUNT (spectrum, "spectrum", 1); + cleanup_spectrum (spectrum); +} + +GST_END_TEST; + +GST_START_TEST (test_float64) +{ + GstElement *spectrum; + GstBuffer *inbuffer, *outbuffer; + GstBus *bus; + GstMessage *message; + const GstStructure *structure; + int i, j; + gdouble *data; + GstMapInfo map; + const GValue *list, *value; + GstClockTime endtime; + gfloat level; + + spectrum = setup_spectrum (SPECT_CAPS_STRING_F64); + g_object_set (spectrum, "post-messages", TRUE, "interval", GST_SECOND / 100, + "bands", SPECT_BANDS, "threshold", -80, NULL); + + fail_unless (gst_element_set_state (spectrum, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + /* create a 1 sec buffer with an 11025 Hz sine wave */ + inbuffer = gst_buffer_new_allocate (NULL, 44100 * sizeof (gdouble), 0); + gst_buffer_map (inbuffer, &map, GST_MAP_WRITE); + data = (gdouble *) map.data; + for (j = 0; j < 44100; j += 4) { + *data = 0.0; + ++data; + *data = 1.0; + ++data; + *data = 0.0; + ++data; + *data = -1.0; + ++data; + } + gst_buffer_unmap (inbuffer, &map); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* create a bus to get the spectrum message on */ + bus = gst_bus_new (); + ASSERT_OBJECT_REFCOUNT (bus, "bus", 1); + gst_element_set_bus (spectrum, bus); + ASSERT_OBJECT_REFCOUNT (bus, "bus", 2); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being collected on the global buffer list */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + + message = gst_bus_poll (bus, GST_MESSAGE_ELEMENT, -1); + ASSERT_OBJECT_REFCOUNT (message, "message", 1); + + fail_unless (message != NULL); + fail_unless (GST_MESSAGE_SRC (message) == GST_OBJECT (spectrum)); + fail_unless (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ELEMENT); + structure = gst_message_get_structure (message); + fail_if (structure == NULL); + fail_unless_equals_string ((char *) gst_structure_get_name (structure), + "spectrum"); + fail_unless (gst_structure_get_clock_time (structure, "endtime", &endtime)); + + list = gst_structure_get_value (structure, "magnitude"); + for (i = 0; i < SPECT_BANDS; ++i) { + value = gst_value_list_get_value (list, i); + level = g_value_get_float (value); + GST_DEBUG ("band[%3d] is %.2f", i, level); + /* Only the bands in the middle should have a level above 60 */ + fail_if ((i == SPECT_BANDS / 2 || i == SPECT_BANDS / 2 - 1) + && level < -20.0); + fail_if ((i != SPECT_BANDS / 2 && i != SPECT_BANDS / 2 - 1) + && level > -20.0); + } + fail_unless_equals_int (g_list_length (buffers), 1); + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + fail_unless (inbuffer == outbuffer); + + /* clean up */ + /* flush current messages,and future state change messages */ + gst_bus_set_flushing (bus, TRUE); + + /* message has a ref to the element */ + ASSERT_OBJECT_REFCOUNT (spectrum, "spectrum", 2); + gst_message_unref (message); + ASSERT_OBJECT_REFCOUNT (spectrum, "spectrum", 1); + + gst_element_set_bus (spectrum, NULL); + ASSERT_OBJECT_REFCOUNT (bus, "bus", 1); + gst_object_unref (bus); + fail_unless (gst_element_set_state (spectrum, + GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS, "could not set to null"); + ASSERT_OBJECT_REFCOUNT (spectrum, "spectrum", 1); + cleanup_spectrum (spectrum); +} + +GST_END_TEST; + + +static Suite * +spectrum_suite (void) +{ + Suite *s = suite_create ("spectrum"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_int16); + tcase_add_test (tc_chain, test_int32); + tcase_add_test (tc_chain, test_float32); + tcase_add_test (tc_chain, test_float64); + + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = spectrum_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/sunaudio.c b/tests/check/elements/sunaudio.c new file mode 100644 index 0000000..834e7cb --- /dev/null +++ b/tests/check/elements/sunaudio.c @@ -0,0 +1,95 @@ +/* GStreamer unit tests for the sun audio elements + * + * Copyright (C) 2007 Tim-Philipp Müller <tim centricular net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <gst/check/gstcheck.h> +#include <gst/interfaces/propertyprobe.h> +#include <gst/interfaces/mixer.h> +#include <gst/gst.h> + +GST_START_TEST (test_sun_audio_mixer_track) +{ + GstStateChangeReturn state_ret; + GstElement *mixer; + GList *tracks, *l; + + mixer = gst_element_factory_make ("sunaudiomixer", "sunaudiomixer"); + fail_unless (mixer != NULL, "Failed to create 'sunaudiomixer' element!"); + + state_ret = gst_element_set_state (mixer, GST_STATE_READY); + if (state_ret != GST_STATE_CHANGE_SUCCESS) { + gst_object_unref (mixer); + return; + } + + GST_LOG ("opened sunaudiomixer"); + fail_unless (GST_IS_MIXER (mixer), "is not a GstMixer?!"); + + tracks = (GList *) gst_mixer_list_tracks (GST_MIXER (mixer)); + for (l = tracks; l != NULL; l = l->next) { + GObjectClass *klass; + GstMixerTrack *track; + gchar *ulabel = NULL, *label = NULL; + + track = GST_MIXER_TRACK (l->data); + + g_object_get (track, "label", &label, NULL); + fail_unless (label == NULL || g_utf8_validate (label, -1, NULL)); + + /* FIXME: remove this check once we depend on -base >= 0.10.12.1 */ + klass = G_OBJECT_GET_CLASS (track); + if (g_object_class_find_property (klass, "untranslated-label")) { + g_object_get (track, "untranslated-label", &ulabel, NULL); + } + + if (ulabel != NULL) { + gchar *p; + + for (p = ulabel; p != NULL && *p != '\0'; ++p) { + fail_unless (g_ascii_isprint (*p), + "untranslated label '%s' not printable ASCII", ulabel); + } + } + GST_DEBUG ("%s: %s", GST_STR_NULL (ulabel), GST_STR_NULL (label)); + g_free (label); + g_free (ulabel); + } + + fail_unless_equals_int (gst_element_set_state (mixer, GST_STATE_NULL), + GST_STATE_CHANGE_SUCCESS); + + gst_object_unref (mixer); +} + +GST_END_TEST; + + +static Suite * +sunaudio_suite (void) +{ + Suite *s = suite_create ("sunaudio"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_sun_audio_mixer_track); + + return s; +} + +GST_CHECK_MAIN (sunaudio) diff --git a/tests/check/elements/udpsink.c b/tests/check/elements/udpsink.c new file mode 100755 index 0000000..0e938ee --- /dev/null +++ b/tests/check/elements/udpsink.c @@ -0,0 +1,222 @@ +/* GStreamer RTP payloader unit tests + * Copyright (C) 2009 Axis Communications <dev-gstreamer@axis.com> + * @author Ognyan Tonchev <ognyan@axis.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. + */ +#include <gst/check/gstcheck.h> +#include <gst/base/gstbasesink.h> +#include <stdlib.h> + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +#define RTP_HEADER_SIZE 12 +#define RTP_PAYLOAD_SIZE 1024 + +/* + * Number of bytes received in the render function when using buffer lists + */ +static guint render_list_bytes_received; + +/* + * Render function for testing udpsink with buffer lists + */ +static GstFlowReturn +udpsink_render_list (GstBaseSink * sink, GstBufferList * list) +{ + guint i, num; + + num = gst_buffer_list_length (list); + for (i = 0; i < num; ++i) { + GstBuffer *buf = gst_buffer_list_get (list, i); + gsize size = gst_buffer_get_size (buf); + + GST_DEBUG ("rendered %" G_GSIZE_FORMAT " bytes", size); + render_list_bytes_received += size; + } + + return GST_FLOW_OK; +} + +static void +set_render_list_function (GstElement * bsink) +{ + GstBaseSinkClass *bsclass; + + bsclass = GST_BASE_SINK_GET_CLASS ((GstBaseSink *) bsink); + + /* Add callback function for the buffer list tests */ + bsclass->render_list = udpsink_render_list; +} + +static GstBufferList * +create_buffer_list (guint * data_size) +{ + GstBufferList *list; + GstBuffer *rtp_buffer; + GstBuffer *data_buffer; + + list = gst_buffer_list_new (); + + /*** First group, i.e. first packet. **/ + + /* Create the RTP header buffer */ + rtp_buffer = gst_buffer_new_allocate (NULL, RTP_HEADER_SIZE, NULL); + gst_buffer_memset (rtp_buffer, 0, 0, RTP_HEADER_SIZE); + + /* Create the buffer that holds the payload */ + data_buffer = gst_buffer_new_allocate (NULL, RTP_PAYLOAD_SIZE, NULL); + gst_buffer_memset (data_buffer, 0, 0, RTP_PAYLOAD_SIZE); + + /* Create a new group to hold the rtp header and the payload */ + gst_buffer_list_add (list, gst_buffer_append (rtp_buffer, data_buffer)); + + /*** Second group, i.e. second packet. ***/ + + /* Create the RTP header buffer */ + rtp_buffer = gst_buffer_new_allocate (NULL, RTP_HEADER_SIZE, NULL); + gst_buffer_memset (rtp_buffer, 0, 0, RTP_HEADER_SIZE); + + /* Create the buffer that holds the payload */ + data_buffer = gst_buffer_new_allocate (NULL, RTP_PAYLOAD_SIZE, NULL); + gst_buffer_memset (data_buffer, 0, 0, RTP_PAYLOAD_SIZE); + + /* Create a new group to hold the rtp header and the payload */ + gst_buffer_list_add (list, gst_buffer_append (rtp_buffer, data_buffer)); + + /* Calculate the size of the data */ + *data_size = 2 * RTP_HEADER_SIZE + 2 * RTP_PAYLOAD_SIZE; + + return list; +} + +static void +udpsink_test (gboolean use_buffer_lists) +{ + GstSegment segment; + GstElement *udpsink; + GstPad *srcpad; + GstBufferList *list; + guint data_size; + + list = create_buffer_list (&data_size); + + udpsink = gst_check_setup_element ("udpsink"); + if (use_buffer_lists) + set_render_list_function (udpsink); + + srcpad = gst_check_setup_src_pad_by_name (udpsink, &srctemplate, "sink"); + + gst_element_set_state (udpsink, GST_STATE_PLAYING); + gst_pad_set_active (srcpad, TRUE); + + gst_pad_push_event (srcpad, gst_event_new_stream_start ("hey there!")); + + gst_segment_init (&segment, GST_FORMAT_TIME); + gst_pad_push_event (srcpad, gst_event_new_segment (&segment)); + + fail_unless_equals_int (gst_pad_push_list (srcpad, list), GST_FLOW_OK); + + gst_check_teardown_pad_by_name (udpsink, "sink"); + gst_check_teardown_element (udpsink); + + if (use_buffer_lists) + fail_unless_equals_int (data_size, render_list_bytes_received); +} + +GST_START_TEST (test_udpsink) +{ + udpsink_test (FALSE); +} + +GST_END_TEST; + + +GST_START_TEST (test_udpsink_bufferlist) +{ + udpsink_test (TRUE); +} + +GST_END_TEST; + +GST_START_TEST (test_udpsink_client_add_remove) +{ + GstElement *udpsink; + + /* Note: keep in mind that these are in addition to the client added by + * the host/port properties (by default 'localhost:5004' */ + + udpsink = gst_check_setup_element ("udpsink"); + g_signal_emit_by_name (udpsink, "remove", "localhost", 5004, NULL); + gst_object_unref (udpsink); + + udpsink = gst_check_setup_element ("udpsink"); + g_signal_emit_by_name (udpsink, "add", "127.0.0.1", 5554, NULL); + gst_object_unref (udpsink); + + udpsink = gst_check_setup_element ("udpsink"); + g_signal_emit_by_name (udpsink, "add", "127.0.0.1", 5554, NULL); + g_signal_emit_by_name (udpsink, "add", "127.0.0.1", 5554, NULL); + gst_object_unref (udpsink); + + udpsink = gst_check_setup_element ("udpsink"); + g_signal_emit_by_name (udpsink, "add", "127.0.0.1", 5554, NULL); + g_signal_emit_by_name (udpsink, "remove", "127.0.0.1", 5554, NULL); + gst_object_unref (udpsink); + + udpsink = gst_check_setup_element ("udpsink"); + g_signal_emit_by_name (udpsink, "add", "127.0.0.1", 5554, NULL); + g_signal_emit_by_name (udpsink, "remove", "127.0.0.1", 5555, NULL); + gst_object_unref (udpsink); + + udpsink = gst_check_setup_element ("udpsink"); + g_signal_emit_by_name (udpsink, "add", "127.0.0.1", 5554, NULL); + g_signal_emit_by_name (udpsink, "add", "127.0.0.1", 5555, NULL); + gst_object_unref (udpsink); + + udpsink = gst_check_setup_element ("udpsink"); + g_signal_emit_by_name (udpsink, "add", "127.0.0.1", 5554, NULL); + g_signal_emit_by_name (udpsink, "add", "10.2.0.1", 5554, NULL); + gst_object_unref (udpsink); + + udpsink = gst_check_setup_element ("udpsink"); + g_signal_emit_by_name (udpsink, "add", "127.0.0.1", 5554, NULL); + g_signal_emit_by_name (udpsink, "add", "10.2.0.1", 5554, NULL); + g_signal_emit_by_name (udpsink, "remove", "127.0.0.1", 5554, NULL); + gst_object_unref (udpsink); +} + +GST_END_TEST; + +static Suite * +udpsink_suite (void) +{ + Suite *s = suite_create ("udpsink_test"); + TCase *tc_chain = tcase_create ("linear"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_udpsink); + tcase_add_test (tc_chain, test_udpsink_bufferlist); + tcase_add_test (tc_chain, test_udpsink_client_add_remove); + + return s; +} + +GST_CHECK_MAIN (udpsink) diff --git a/tests/check/elements/udpsrc.c b/tests/check/elements/udpsrc.c new file mode 100644 index 0000000..1a0ab3c --- /dev/null +++ b/tests/check/elements/udpsrc.c @@ -0,0 +1,123 @@ +/* GStreamer UDP source unit tests + * Copyright (C) 2011 Tim-Philipp Müller <tim centricular net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#include <gst/check/gstcheck.h> +#include <gio/gio.h> +#include <stdlib.h> +#include <unistd.h> + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +GST_START_TEST (test_udpsrc_empty_packet) +{ + GstElement *udpsrc; + GSocket *socket; + GstPad *sinkpad; + int port = 0; + + udpsrc = gst_check_setup_element ("udpsrc"); + fail_unless (udpsrc != NULL); + g_object_set (udpsrc, "port", 0, NULL); + + sinkpad = gst_check_setup_sink_pad_by_name (udpsrc, &sinktemplate, "src"); + fail_unless (sinkpad != NULL); + gst_pad_set_active (sinkpad, TRUE); + + gst_element_set_state (udpsrc, GST_STATE_PLAYING); + g_object_get (udpsrc, "port", &port, NULL); + GST_INFO ("udpsrc port = %d", port); + + socket = g_socket_new (G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_DATAGRAM, + G_SOCKET_PROTOCOL_UDP, NULL); + + if (socket != NULL) { + GSocketAddress *sa; + GInetAddress *ia; + gchar *s; + + ia = g_inet_address_new_loopback (G_SOCKET_FAMILY_IPV4); + s = g_inet_address_to_string (ia); + GST_LOG ("inet address %s", s); + g_free (s); + sa = g_inet_socket_address_new (ia, port); + + if (g_socket_send_to (socket, sa, "HeLL0", 0, NULL, NULL) == 0) { + GST_INFO ("sent 0 bytes"); + if (g_socket_send_to (socket, sa, "HeLL0", 6, NULL, NULL) == 6) { + GstMapInfo map; + GstBuffer *buf; + guint len; + + GST_INFO ("sent 6 bytes"); + + g_usleep (G_USEC_PER_SEC / 2); + + len = g_list_length (buffers); + GST_INFO ("%u buffers", len); + fail_unless (len == 1 || len == 2); + + /* last buffer should be our HeLL0 string */ + buf = GST_BUFFER (g_list_nth_data (buffers, len - 1)); + gst_buffer_map (buf, &map, GST_MAP_READ); + fail_unless_equals_int (map.size, 6); + fail_unless_equals_string ((gchar *) map.data, "HeLL0"); + gst_buffer_unmap (buf, &map); + + /* if there's another buffer, it should be 0 bytes */ + if (len == 2) { + buf = GST_BUFFER (g_list_nth_data (buffers, 0)); + fail_unless_equals_int (gst_buffer_get_size (buf), 0); + } + } else { + GST_WARNING ("send_to(6 bytes) failed"); + } + } else { + GST_WARNING ("send_to(0 bytes) failed"); + } + + g_object_unref (sa); + g_object_unref (ia); + } else { + GST_WARNING ("Could not create IPv4 UDP socket for unit test"); + } + + gst_element_set_state (udpsrc, GST_STATE_NULL); + + gst_check_teardown_pad_by_name (udpsrc, "src"); + gst_check_teardown_element (udpsrc); + + g_object_unref (socket); +} + +GST_END_TEST; + +static Suite * +udpsrc_suite (void) +{ + Suite *s = suite_create ("udpsrc"); + TCase *tc_chain = tcase_create ("udpsrc"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_udpsrc_empty_packet); + return s; +} + +GST_CHECK_MAIN (udpsrc) diff --git a/tests/check/elements/videobox.c b/tests/check/elements/videobox.c new file mode 100755 index 0000000..8953449 --- /dev/null +++ b/tests/check/elements/videobox.c @@ -0,0 +1,238 @@ +/* GStreamer + * unit test for the videobox element + * + * Copyright (C) 2006 Ravi Kiran K N <ravi.kiran@samsung.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 + +#include <unistd.h> + +#include <gst/check/gstcheck.h> + +typedef struct _GstVideoBoxTestContext +{ + GstElement *pipeline; + GstElement *src; + GstElement *filter; + GstElement *box; + GstElement *filter2; + GstElement *sink; +} GstVideoBoxTestContext; + +typedef struct _FormatConversion +{ + const gchar *in_caps; + const gchar *out_caps; + gboolean expected_result; +} FormatConversion; + + +/* + * Update this table as and when the conversion is supported(or unsupported) in videobox + */ +static const FormatConversion conversion_table[] = { + {"video/x-raw,format={RGBA}", "video/x-raw,format={AYUV}", TRUE}, + {"video/x-raw,format={AYUV}", "video/x-raw,format={RGBA}", TRUE}, + {"video/x-raw,format={I420}", "video/x-raw,format={AYUV}", TRUE}, + {"video/x-raw,format={AYUV}", "video/x-raw,format={I420}", TRUE}, + {"video/x-raw,format={I420}", "video/x-raw,format={YV12}", TRUE}, + {"video/x-raw,format={YV12}", "video/x-raw,format={AYUV}", TRUE}, + {"video/x-raw,format={YV12}", "video/x-raw,format={I420}", TRUE}, + {"video/x-raw,format={AYUV}", "video/x-raw,format={YV12}", TRUE}, + {"video/x-raw,format={AYUV}", "video/x-raw,format={xRGB}", TRUE}, + {"video/x-raw,format={xRGB}", "video/x-raw,format={xRGB}", TRUE}, + {"video/x-raw,format={xRGB}", "video/x-raw,format={AYUV}", TRUE}, + {"video/x-raw,format={GRAY8}", "video/x-raw,format={GRAY16_LE}", FALSE}, + {"video/x-raw,format={GRAY8}", "video/x-raw,format={GRAY16_BE}", FALSE}, + {"video/x-raw,format={Y444}", "video/x-raw,format={Y42B}", FALSE}, + {"video/x-raw,format={Y444}", "video/x-raw,format={Y41B}", FALSE}, + {"video/x-raw,format={Y42B}", "video/x-raw,format={Y41B}", FALSE} +}; + + +static gboolean +bus_handler (GstBus * bus, GstMessage * message, gpointer data) +{ + GMainLoop *loop = (GMainLoop *) data; + + switch (message->type) { + case GST_MESSAGE_EOS:{ + GST_LOG ("EOS event received"); + g_main_loop_quit (loop); + break; + } + case GST_MESSAGE_ERROR:{ + GError *gerror; + gchar *debug; + gst_message_parse_error (message, &gerror, &debug); + g_error ("Error from %s: %s (%s)\n", + GST_ELEMENT_NAME (GST_MESSAGE_SRC (message)), gerror->message, + GST_STR_NULL (debug)); + g_error_free (gerror); + g_free (debug); + g_main_loop_quit (loop); + break; + } + case GST_MESSAGE_WARNING:{ + g_main_loop_quit (loop); + break; + } + default: + break; + } + + return TRUE; +} + +static void +videobox_test_init_context (GstVideoBoxTestContext * ctx) +{ + fail_unless (ctx != NULL); + + ctx->pipeline = gst_pipeline_new ("pipeline"); + fail_unless (ctx->pipeline != NULL); + ctx->src = gst_element_factory_make ("videotestsrc", "src"); + fail_unless (ctx->src != NULL, "Failed to create videotestsrc element"); + ctx->filter = gst_element_factory_make ("capsfilter", "filter"); + fail_unless (ctx->filter != NULL, "Failed to create capsfilter element"); + ctx->box = gst_element_factory_make ("videobox", "box"); + fail_unless (ctx->box != NULL, "Failed to create videobox element"); + ctx->filter2 = gst_element_factory_make ("capsfilter", "filter2"); + fail_unless (ctx->filter2 != NULL, + "Failed to create second capsfilter element"); + ctx->sink = gst_element_factory_make ("fakesink", "sink"); + fail_unless (ctx->sink != NULL, "Failed to create fakesink element"); + + gst_bin_add_many (GST_BIN (ctx->pipeline), ctx->src, ctx->filter, + ctx->box, ctx->filter2, ctx->sink, NULL); + fail_unless (gst_element_link_many (ctx->src, ctx->filter, ctx->box, + ctx->filter2, ctx->sink, NULL) == TRUE, "Can not link elements"); + + fail_unless (gst_element_set_state (ctx->pipeline, + GST_STATE_READY) != GST_STATE_CHANGE_FAILURE, + "couldn't set pipeline to READY state"); + + GST_LOG ("videobox context inited"); +} + +static void +videobox_test_deinit_context (GstVideoBoxTestContext * ctx) +{ + GST_LOG ("deiniting videobox context"); + + gst_element_set_state (ctx->pipeline, GST_STATE_NULL); + gst_object_unref (ctx->pipeline); + memset (ctx, 0x00, sizeof (GstVideoBoxTestContext)); +} + +GST_START_TEST (test_caps_transform) +{ + GstStateChangeReturn state_ret; + GstVideoBoxTestContext ctx; + guint conversions_test_size; + guint itr; + gboolean link_res; + GMainLoop *loop; + GstBus *bus; + + videobox_test_init_context (&ctx); + gst_util_set_object_arg (G_OBJECT (ctx.src), "num-buffers", "1"); + + loop = g_main_loop_new (NULL, TRUE); + fail_unless (loop != NULL); + + bus = gst_element_get_bus (ctx.pipeline); + fail_unless (bus != NULL); + + gst_bus_add_watch (bus, bus_handler, loop); + gst_object_unref (bus); + + conversions_test_size = G_N_ELEMENTS (conversion_table); + for (itr = 0; itr < conversions_test_size; itr++) { + gst_element_unlink_many (ctx.src, ctx.filter, ctx.box, ctx.filter2, + ctx.sink, NULL); + gst_util_set_object_arg (G_OBJECT (ctx.filter), "caps", + conversion_table[itr].in_caps); + gst_util_set_object_arg (G_OBJECT (ctx.filter2), "caps", + conversion_table[itr].out_caps); + + /* Link with new input and output format from conversion table */ + link_res = + gst_element_link_many (ctx.src, ctx.filter, ctx.box, ctx.filter2, + ctx.sink, NULL); + + /* Check if the specified format conversion is supported or not by videobox */ + fail_unless (link_res == conversion_table[itr].expected_result, + "videobox can not convert from '%s'' to '%s'", + conversion_table[itr].in_caps, conversion_table[itr].out_caps); + + if (link_res == FALSE) { + GST_LOG ("elements linking failed"); + continue; + } + + state_ret = gst_element_set_state (ctx.pipeline, GST_STATE_PLAYING); + fail_unless (state_ret != GST_STATE_CHANGE_FAILURE, + "couldn't set pipeline to PLAYING state"); + + g_main_loop_run (loop); + + state_ret = gst_element_set_state (ctx.pipeline, GST_STATE_READY); + fail_unless (state_ret != GST_STATE_CHANGE_FAILURE, + "couldn't set pipeline to READY state"); + } + + g_main_loop_unref (loop); + + videobox_test_deinit_context (&ctx); +} + +GST_END_TEST; + + +static Suite * +videobox_suite (void) +{ + Suite *s = suite_create ("videobox"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_caps_transform); + + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = videobox_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/videocrop.c b/tests/check/elements/videocrop.c new file mode 100644 index 0000000..a7bb46d --- /dev/null +++ b/tests/check/elements/videocrop.c @@ -0,0 +1,834 @@ +/* GStreamer unit test for the videocrop element + * Copyright (C) 2006 Tim-Philipp Müller <tim centricular net> + * + * 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 + +#ifdef HAVE_VALGRIND +# include <valgrind/valgrind.h> +#endif + +#include <unistd.h> + +#include <gst/check/gstcheck.h> +#include <gst/video/video.h> +#include <gst/base/gstbasetransform.h> + +/* return a list of caps where we only need to set + * width and height to get fixed caps */ +static GList * +video_crop_get_test_caps (GstElement * videocrop) +{ + GstCaps *templ, *allowed_caps; + GstPad *srcpad; + GList *list = NULL; + guint i; + + srcpad = gst_element_get_static_pad (videocrop, "src"); + fail_unless (srcpad != NULL); + templ = gst_pad_get_pad_template_caps (srcpad); + fail_unless (templ != NULL); + + allowed_caps = gst_caps_normalize (templ); + + for (i = 0; i < gst_caps_get_size (allowed_caps); ++i) { + GstStructure *new_structure; + GstCaps *single_caps; + + single_caps = gst_caps_new_empty (); + new_structure = + gst_structure_copy (gst_caps_get_structure (allowed_caps, i)); + gst_structure_set (new_structure, "framerate", GST_TYPE_FRACTION, + 1, 1, NULL); + gst_structure_remove_field (new_structure, "width"); + gst_structure_remove_field (new_structure, "height"); + gst_caps_append_structure (single_caps, new_structure); + + GST_DEBUG ("have caps %" GST_PTR_FORMAT, single_caps); + /* should be fixed without width/height */ + fail_unless (gst_caps_is_fixed (single_caps)); + + list = g_list_prepend (list, single_caps); + } + + gst_caps_unref (allowed_caps); + gst_object_unref (srcpad); + + return list; +} + +GST_START_TEST (test_unit_sizes) +{ + GstBaseTransformClass *csp_klass, *vcrop_klass; + GstElement *videocrop, *csp; + GList *caps_list, *l; + + videocrop = gst_element_factory_make ("videocrop", "videocrop"); + fail_unless (videocrop != NULL, "Failed to create videocrop element"); + vcrop_klass = GST_BASE_TRANSFORM_GET_CLASS (videocrop); + + csp = gst_element_factory_make ("videoconvert", "csp"); + fail_unless (csp != NULL, "Failed to create videoconvert element"); + csp_klass = GST_BASE_TRANSFORM_GET_CLASS (csp); + + caps_list = video_crop_get_test_caps (videocrop); + + for (l = caps_list; l != NULL; l = l->next) { + const struct + { + gint width, height; + } sizes_to_try[] = { + { + 160, 120}, { + 161, 120}, { + 160, 121}, { + 161, 121}, { + 159, 120}, { + 160, 119}, { + 159, 119}, { + 159, 121} + }; + GstStructure *s; + GstCaps *caps; + gint i; + + caps = gst_caps_copy (GST_CAPS (l->data)); + s = gst_caps_get_structure (caps, 0); + fail_unless (s != NULL); + + for (i = 0; i < G_N_ELEMENTS (sizes_to_try); ++i) { + gchar *caps_str; + gsize csp_size = 0; + gsize vc_size = 0; + + gst_structure_set (s, "width", G_TYPE_INT, sizes_to_try[i].width, + "height", G_TYPE_INT, sizes_to_try[i].height, NULL); + + caps_str = gst_caps_to_string (caps); + GST_INFO ("Testing unit size for %s", caps_str); + + /* skip if videoconvert doesn't support these caps + * (only works with gst-plugins-base 0.10.9.1 or later) */ + if (!csp_klass->get_unit_size ((GstBaseTransform *) csp, caps, &csp_size)) { + GST_INFO ("videoconvert does not support format %s", caps_str); + g_free (caps_str); + continue; + } + + fail_unless (vcrop_klass->get_unit_size ((GstBaseTransform *) videocrop, + caps, &vc_size)); + + fail_unless (vc_size == csp_size, + "videocrop and videoconvert return different unit sizes for " + "caps %s: vc_size=%d, csp_size=%d", caps_str, vc_size, csp_size); + + g_free (caps_str); + } + + gst_caps_unref (caps); + } + + g_list_foreach (caps_list, (GFunc) gst_caps_unref, NULL); + g_list_free (caps_list); + + gst_object_unref (csp); + gst_object_unref (videocrop); +} + +GST_END_TEST; + +typedef struct +{ + GstElement *pipeline; + GstElement *src; + GstElement *filter; + GstElement *crop; + GstElement *filter2; + GstElement *sink; + GstBuffer *last_buf; + GstCaps *last_caps; +} GstVideoCropTestContext; + +static void +handoff_cb (GstElement * sink, GstBuffer * buf, GstPad * pad, + GstVideoCropTestContext * ctx) +{ + GstCaps *caps; + + gst_buffer_replace (&ctx->last_buf, buf); + caps = gst_pad_get_current_caps (pad); + gst_caps_replace (&ctx->last_caps, caps); + gst_caps_unref (caps); +} + +static void +videocrop_test_cropping_init_context (GstVideoCropTestContext * ctx) +{ + fail_unless (ctx != NULL); + + ctx->pipeline = gst_pipeline_new ("pipeline"); + fail_unless (ctx->pipeline != NULL); + ctx->src = gst_element_factory_make ("videotestsrc", "src"); + fail_unless (ctx->src != NULL, "Failed to create videotestsrc element"); + ctx->filter = gst_element_factory_make ("capsfilter", "filter"); + fail_unless (ctx->filter != NULL, "Failed to create capsfilter element"); + ctx->crop = gst_element_factory_make ("videocrop", "crop"); + fail_unless (ctx->crop != NULL, "Failed to create videocrop element"); + ctx->filter2 = gst_element_factory_make ("capsfilter", "filter2"); + fail_unless (ctx->filter2 != NULL, + "Failed to create second capsfilter element"); + ctx->sink = gst_element_factory_make ("fakesink", "sink"); + fail_unless (ctx->sink != NULL, "Failed to create fakesink element"); + + gst_bin_add_many (GST_BIN (ctx->pipeline), ctx->src, ctx->filter, + ctx->crop, ctx->filter2, ctx->sink, NULL); + gst_element_link_many (ctx->src, ctx->filter, ctx->crop, ctx->filter2, + ctx->sink, NULL); + + /* set pattern to 'red' - for our purposes it doesn't matter anyway */ + g_object_set (ctx->src, "pattern", 4, NULL); + + g_object_set (ctx->sink, "signal-handoffs", TRUE, NULL); + g_signal_connect (ctx->sink, "preroll-handoff", G_CALLBACK (handoff_cb), ctx); + + ctx->last_buf = NULL; + ctx->last_caps = NULL; + + GST_LOG ("context inited"); +} + +static void +videocrop_test_cropping_deinit_context (GstVideoCropTestContext * ctx) +{ + GST_LOG ("deiniting context"); + + gst_element_set_state (ctx->pipeline, GST_STATE_NULL); + gst_object_unref (ctx->pipeline); + gst_buffer_replace (&ctx->last_buf, NULL); + gst_caps_replace (&ctx->last_caps, NULL); + memset (ctx, 0x00, sizeof (GstVideoCropTestContext)); +} + +typedef void (*GstVideoCropTestBufferFunc) (GstBuffer * buffer, GstCaps * caps); + +static void +videocrop_test_cropping (GstVideoCropTestContext * ctx, GstCaps * in_caps, + GstCaps * out_caps, gint left, gint right, gint top, gint bottom, + GstVideoCropTestBufferFunc func) +{ + GST_LOG ("lrtb = %03u %03u %03u %03u, in_caps = %" GST_PTR_FORMAT + ", out_caps = %" GST_PTR_FORMAT, left, right, top, bottom, in_caps, + out_caps); + + g_object_set (ctx->filter, "caps", in_caps, NULL); + g_object_set (ctx->filter2, "caps", out_caps, NULL); + + g_object_set (ctx->crop, "left", left, "right", right, "top", top, + "bottom", bottom, NULL); + + /* this will fail if videotestsrc doesn't support our format; we need + * videotestsrc from -base CVS 0.10.9.1 with RGBA and AYUV support */ + fail_unless (gst_element_set_state (ctx->pipeline, + GST_STATE_PAUSED) != GST_STATE_CHANGE_FAILURE); + fail_unless (gst_element_get_state (ctx->pipeline, NULL, NULL, + -1) == GST_STATE_CHANGE_SUCCESS); + + if (func != NULL) { + func (ctx->last_buf, ctx->last_caps); + } + + gst_element_set_state (ctx->pipeline, GST_STATE_NULL); +} + +static void +check_1x1_buffer (GstBuffer * buf, GstCaps * caps) +{ + GstVideoInfo info; + GstVideoFrame frame; + /* the exact values we check for come from videotestsrc */ + static const guint yuv_values[] = { 81, 90, 240, 255 }; + static const guint rgb_values[] = { 0xff, 0, 0, 255 }; + static const guint gray8_values[] = { 0x51 }; + static const guint gray16_values[] = { 0x5151 }; + const guint *values; + guint i; + const GstVideoFormatInfo *finfo; + + fail_unless (buf != NULL); + fail_unless (caps != NULL); + + fail_unless (gst_video_info_from_caps (&info, caps)); + fail_unless (gst_video_frame_map (&frame, &info, buf, GST_MAP_READ)); + + finfo = info.finfo; + + if (GST_VIDEO_INFO_IS_YUV (&info)) + values = yuv_values; + else if (GST_VIDEO_INFO_IS_GRAY (&info)) + if (GST_VIDEO_FORMAT_INFO_BITS (finfo) == 8) + values = gray8_values; + else + values = gray16_values; + else + values = rgb_values; + + GST_MEMDUMP ("buffer", GST_VIDEO_FRAME_PLANE_DATA (&frame, 0), 8); + + for (i = 0; i < GST_VIDEO_FRAME_N_COMPONENTS (&frame); i++) { + guint8 *data = GST_VIDEO_FRAME_COMP_DATA (&frame, i); + + GST_DEBUG ("W: %d", GST_VIDEO_FORMAT_INFO_W_SUB (finfo, i)); + GST_DEBUG ("H: %d", GST_VIDEO_FORMAT_INFO_H_SUB (finfo, i)); + + if (GST_VIDEO_FORMAT_INFO_W_SUB (finfo, + i) >= GST_VIDEO_FRAME_WIDTH (&frame)) + continue; + if (GST_VIDEO_FORMAT_INFO_H_SUB (finfo, + i) >= GST_VIDEO_FRAME_HEIGHT (&frame)) + continue; + + if (GST_VIDEO_FORMAT_INFO_BITS (finfo) == 8) { + fail_unless_equals_int (data[0], values[i]); + } else if (GST_VIDEO_FORMAT_INFO_BITS (finfo) == 16) { + guint16 pixels, val; + gint depth; + + if (GST_VIDEO_FORMAT_INFO_IS_LE (finfo)) + pixels = GST_READ_UINT16_LE (data); + else + pixels = GST_READ_UINT16_BE (data); + + depth = GST_VIDEO_FORMAT_INFO_DEPTH (finfo, i); + val = pixels >> GST_VIDEO_FORMAT_INFO_SHIFT (finfo, i); + val = val & ((1 << depth) - 1); + + GST_DEBUG ("val %08x %d : %d", pixels, i, val); + if (depth <= 8) { + fail_unless_equals_int (val, values[i] >> (8 - depth)); + } else { + fail_unless_equals_int (val, values[i] >> (16 - depth)); + } + } else { + } + } + + gst_video_frame_unmap (&frame); + + /* + fail_unless_equals_int ((pixel & rmask) >> rshift, 0xff); + fail_unless_equals_int ((pixel & gmask) >> gshift, 0x00); + fail_unless_equals_int ((pixel & bmask) >> bshift, 0x00); + */ +} + +GST_START_TEST (test_crop_to_1x1) +{ + GstVideoCropTestContext ctx; + GList *caps_list, *node; + + videocrop_test_cropping_init_context (&ctx); + + caps_list = video_crop_get_test_caps (ctx.crop); + + for (node = caps_list; node != NULL; node = node->next) { + GstStructure *s; + GstCaps *caps; + + caps = gst_caps_copy (GST_CAPS (node->data)); + s = gst_caps_get_structure (caps, 0); + fail_unless (s != NULL); + + GST_INFO ("testing format: %" GST_PTR_FORMAT, caps); + + gst_structure_set (s, "width", G_TYPE_INT, 160, + "height", G_TYPE_INT, 160, NULL); + + videocrop_test_cropping (&ctx, caps, NULL, 159, 0, 159, 0, + check_1x1_buffer); + /* commented out because they don't really add anything useful check-wise: + videocrop_test_cropping (&ctx, caps, NULL, 0, 159, 0, 159, check_1x1_buffer); + videocrop_test_cropping (&ctx, caps, NULL, 159, 0, 0, 159, check_1x1_buffer); + videocrop_test_cropping (&ctx, caps, NULL, 0, 159, 159, 0, check_1x1_buffer); + */ + gst_caps_unref (caps); + } + g_list_foreach (caps_list, (GFunc) gst_caps_unref, NULL); + g_list_free (caps_list); + + videocrop_test_cropping_deinit_context (&ctx); +} + +GST_END_TEST; + +GST_START_TEST (test_cropping) +{ + GstVideoCropTestContext ctx; + struct + { + gint width, height; + } sizes_to_try[] = { + { + 160, 160}, { + 161, 160}, { + 160, 161}, { + 161, 161}, { + 159, 160}, { + 160, 159}, { + 159, 159}, { + 159, 161} + }; + GList *caps_list, *node; + gint i; + + videocrop_test_cropping_init_context (&ctx); + + caps_list = video_crop_get_test_caps (ctx.crop); + node = g_list_nth (caps_list, __i__); + + if (node != NULL) { + GstStructure *s; + GstCaps *caps; + + caps = gst_caps_copy (GST_CAPS (node->data)); + s = gst_caps_get_structure (caps, 0); + fail_unless (s != NULL); + + GST_INFO ("testing format: %" GST_PTR_FORMAT, caps); + + for (i = 0; i < G_N_ELEMENTS (sizes_to_try); ++i) { + GstCaps *in_caps, *out_caps; + + GST_INFO (" - %d x %d", sizes_to_try[i].width, sizes_to_try[i].height); + + gst_structure_set (s, "width", G_TYPE_INT, sizes_to_try[i].width, + "height", G_TYPE_INT, sizes_to_try[i].height, NULL); + in_caps = gst_caps_copy (caps); + + gst_structure_set (s, "width", G_TYPE_INT, 1, "height", G_TYPE_INT, 1, + NULL); + out_caps = gst_caps_copy (caps); + + videocrop_test_cropping (&ctx, in_caps, NULL, 0, 0, 0, 0, NULL); + videocrop_test_cropping (&ctx, in_caps, NULL, 1, 0, 0, 0, NULL); + videocrop_test_cropping (&ctx, in_caps, NULL, 0, 1, 0, 0, NULL); + videocrop_test_cropping (&ctx, in_caps, NULL, 0, 0, 1, 0, NULL); + videocrop_test_cropping (&ctx, in_caps, NULL, 0, 0, 0, 1, NULL); + videocrop_test_cropping (&ctx, in_caps, NULL, 63, 0, 0, 0, NULL); + videocrop_test_cropping (&ctx, in_caps, NULL, 0, 63, 0, 0, NULL); + videocrop_test_cropping (&ctx, in_caps, NULL, 0, 0, 63, 0, NULL); + videocrop_test_cropping (&ctx, in_caps, NULL, 0, 0, 0, 63, NULL); + videocrop_test_cropping (&ctx, in_caps, NULL, 63, 0, 0, 1, NULL); + videocrop_test_cropping (&ctx, in_caps, NULL, 0, 63, 1, 0, NULL); + videocrop_test_cropping (&ctx, in_caps, NULL, 0, 1, 63, 0, NULL); + videocrop_test_cropping (&ctx, in_caps, NULL, 1, 0, 0, 63, NULL); + videocrop_test_cropping (&ctx, in_caps, NULL, 0, 0, 0, 0, NULL); + videocrop_test_cropping (&ctx, in_caps, NULL, 32, 0, 0, 128, NULL); + videocrop_test_cropping (&ctx, in_caps, NULL, 0, 32, 128, 0, NULL); + videocrop_test_cropping (&ctx, in_caps, NULL, 0, 128, 32, 0, NULL); + videocrop_test_cropping (&ctx, in_caps, NULL, 128, 0, 0, 32, NULL); + videocrop_test_cropping (&ctx, in_caps, NULL, 1, 1, 1, 1, NULL); + videocrop_test_cropping (&ctx, in_caps, NULL, 63, 63, 63, 63, NULL); + videocrop_test_cropping (&ctx, in_caps, NULL, 64, 64, 64, 64, NULL); + + /* Dynamic cropping */ + videocrop_test_cropping (&ctx, in_caps, out_caps, -1, -1, -1, -1, NULL); + videocrop_test_cropping (&ctx, in_caps, out_caps, 0, -1, -1, -1, NULL); + videocrop_test_cropping (&ctx, in_caps, out_caps, -1, 0, -1, -1, NULL); + videocrop_test_cropping (&ctx, in_caps, out_caps, -1, -1, 0, -1, NULL); + videocrop_test_cropping (&ctx, in_caps, out_caps, -1, -1, -1, 0, NULL); + videocrop_test_cropping (&ctx, in_caps, out_caps, 10, -1, 10, -1, NULL); + videocrop_test_cropping (&ctx, in_caps, out_caps, -1, 10, -1, 10, NULL); + videocrop_test_cropping (&ctx, in_caps, out_caps, + sizes_to_try[i].width - 1, -1, -1, -1, NULL); + videocrop_test_cropping (&ctx, in_caps, out_caps, -1, + sizes_to_try[i].width - 1, -1, -1, NULL); + videocrop_test_cropping (&ctx, in_caps, out_caps, -1, -1, + sizes_to_try[i].height - 1, -1, NULL); + videocrop_test_cropping (&ctx, in_caps, out_caps, -1, -1, -1, + sizes_to_try[i].height - 1, NULL); + + gst_caps_unref (in_caps); + gst_caps_unref (out_caps); + } + + gst_caps_unref (caps); + } else { + GST_INFO ("no caps #%d", __i__); + } + g_list_foreach (caps_list, (GFunc) gst_caps_unref, NULL); + g_list_free (caps_list); + + videocrop_test_cropping_deinit_context (&ctx); +} + +GST_END_TEST; + + +static GstPadProbeReturn +buffer_probe_cb (GstPad * pad, GstPadProbeInfo * info, gpointer data) +{ + GstBuffer **p_buf = data; + GstBuffer *buf = GST_PAD_PROBE_INFO_BUFFER (info); + + gst_buffer_replace (p_buf, buf); + + return GST_PAD_PROBE_OK; /* keep data */ +} + +GST_START_TEST (test_passthrough) +{ + GstStateChangeReturn state_ret; + GstVideoCropTestContext ctx; + GstPad *srcpad; + GstBuffer *gen_buf = NULL; /* buffer generated by videotestsrc */ + + videocrop_test_cropping_init_context (&ctx); + + g_object_set (ctx.src, "num-buffers", 1, NULL); + + srcpad = gst_element_get_static_pad (ctx.src, "src"); + fail_unless (srcpad != NULL); + gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_BUFFER, buffer_probe_cb, + &gen_buf, NULL); + gst_object_unref (srcpad); + + g_object_set (ctx.crop, "left", 0, "right", 0, "top", 0, "bottom", 0, NULL); + + state_ret = gst_element_set_state (ctx.pipeline, GST_STATE_PAUSED); + fail_unless (state_ret != GST_STATE_CHANGE_FAILURE, + "couldn't set pipeline to PAUSED state"); + + state_ret = gst_element_get_state (ctx.pipeline, NULL, NULL, -1); + fail_unless (state_ret == GST_STATE_CHANGE_SUCCESS, + "pipeline failed to go to PAUSED state"); + + fail_unless (gen_buf != NULL); + fail_unless (ctx.last_buf != NULL); + + /* pass through should do nothing */ + fail_unless (gen_buf == ctx.last_buf); + + videocrop_test_cropping_deinit_context (&ctx); + + fail_unless_equals_int (GST_MINI_OBJECT_REFCOUNT_VALUE (gen_buf), 1); + gst_buffer_unref (gen_buf); +} + +GST_END_TEST; + +static gint +notgst_value_list_get_nth_int (const GValue * list_val, guint n) +{ + const GValue *v; + + fail_unless (GST_VALUE_HOLDS_LIST (list_val)); + fail_unless (n < gst_value_list_get_size (list_val)); + + v = gst_value_list_get_value (list_val, n); + fail_unless (G_VALUE_HOLDS_INT (v)); + return g_value_get_int (v); +} + +GST_START_TEST (test_caps_transform) +{ + GstVideoCropTestContext ctx; + GstBaseTransformClass *klass; + GstBaseTransform *crop; + const GValue *w_val; + const GValue *h_val; + GstCaps *caps, *adj_caps; + + videocrop_test_cropping_init_context (&ctx); + + crop = GST_BASE_TRANSFORM (ctx.crop); + klass = GST_BASE_TRANSFORM_GET_CLASS (ctx.crop); + fail_unless (klass != NULL); + + caps = gst_caps_new_simple ("video/x-raw", + "format", G_TYPE_STRING, "I420", + "framerate", GST_TYPE_FRACTION, 1, 1, + "width", G_TYPE_INT, 200, "height", G_TYPE_INT, 100, NULL); + + /* by default, it should be no cropping and hence passthrough */ + adj_caps = klass->transform_caps (crop, GST_PAD_SRC, caps, NULL); + fail_unless (adj_caps != NULL); + fail_unless (gst_caps_is_equal (adj_caps, caps)); + gst_caps_unref (adj_caps); + + adj_caps = klass->transform_caps (crop, GST_PAD_SINK, caps, NULL); + fail_unless (adj_caps != NULL); + fail_unless (gst_caps_is_equal (adj_caps, caps)); + gst_caps_unref (adj_caps); + + /* make sure that's still true after changing properties back and forth */ + g_object_set (ctx.crop, "left", 1, "right", 3, "top", 5, "bottom", 7, NULL); + g_object_set (ctx.crop, "left", 0, "right", 0, "top", 0, "bottom", 0, NULL); + + adj_caps = klass->transform_caps (crop, GST_PAD_SRC, caps, NULL); + fail_unless (adj_caps != NULL); + fail_unless (gst_caps_is_equal (adj_caps, caps)); + gst_caps_unref (adj_caps); + + adj_caps = klass->transform_caps (crop, GST_PAD_SINK, caps, NULL); + fail_unless (adj_caps != NULL); + fail_unless (gst_caps_is_equal (adj_caps, caps)); + gst_caps_unref (adj_caps); + + /* now check adjustments made ... */ + g_object_set (ctx.crop, "left", 1, "right", 3, "top", 5, "bottom", 7, NULL); + + /* ========= (1) fixed value ============================================= */ + + /* sink => source, source must be bigger if we crop stuff off */ + adj_caps = klass->transform_caps (crop, GST_PAD_SRC, caps, NULL); + fail_unless (adj_caps != NULL); + fail_unless (gst_caps_get_size (adj_caps) == 1); + w_val = + gst_structure_get_value (gst_caps_get_structure (adj_caps, 0), "width"); + fail_unless (w_val != NULL); + fail_unless (G_VALUE_HOLDS_INT (w_val)); + fail_unless_equals_int (g_value_get_int (w_val), 200 + (1 + 3)); + h_val = + gst_structure_get_value (gst_caps_get_structure (adj_caps, 0), "height"); + fail_unless (h_val != NULL); + fail_unless (G_VALUE_HOLDS_INT (h_val)); + fail_unless_equals_int (g_value_get_int (h_val), 100 + (5 + 7)); + gst_caps_unref (adj_caps); + + /* source => sink becomes smaller */ + adj_caps = klass->transform_caps (crop, GST_PAD_SINK, caps, NULL); + fail_unless (adj_caps != NULL); + fail_unless (gst_caps_get_size (adj_caps) == 1); + w_val = + gst_structure_get_value (gst_caps_get_structure (adj_caps, 0), "width"); + fail_unless (w_val != NULL); + fail_unless (G_VALUE_HOLDS_INT (w_val)); + fail_unless_equals_int (g_value_get_int (w_val), 200 - (1 + 3)); + h_val = + gst_structure_get_value (gst_caps_get_structure (adj_caps, 0), "height"); + fail_unless (h_val != NULL); + fail_unless (G_VALUE_HOLDS_INT (h_val)); + fail_unless_equals_int (g_value_get_int (h_val), 100 - (5 + 7)); + gst_caps_unref (adj_caps); + + /* ========= (2) range (simple adjustment) =============================== */ + + gst_structure_set (gst_caps_get_structure (caps, 0), + "width", GST_TYPE_INT_RANGE, 1000, 2000, + "height", GST_TYPE_INT_RANGE, 3000, 4000, NULL); + + /* sink => source, source must be bigger if we crop stuff off */ + adj_caps = klass->transform_caps (crop, GST_PAD_SRC, caps, NULL); + fail_unless (adj_caps != NULL); + fail_unless (gst_caps_get_size (adj_caps) == 1); + w_val = + gst_structure_get_value (gst_caps_get_structure (adj_caps, 0), "width"); + fail_unless (w_val != NULL); + fail_unless (GST_VALUE_HOLDS_INT_RANGE (w_val)); + fail_unless_equals_int (gst_value_get_int_range_min (w_val), 1000 + (1 + 3)); + fail_unless_equals_int (gst_value_get_int_range_max (w_val), 2000 + (1 + 3)); + h_val = + gst_structure_get_value (gst_caps_get_structure (adj_caps, 0), "height"); + fail_unless (h_val != NULL); + fail_unless (GST_VALUE_HOLDS_INT_RANGE (h_val)); + fail_unless_equals_int (gst_value_get_int_range_min (h_val), 3000 + (5 + 7)); + fail_unless_equals_int (gst_value_get_int_range_max (h_val), 4000 + (5 + 7)); + gst_caps_unref (adj_caps); + + /* source => sink becomes smaller */ + adj_caps = klass->transform_caps (crop, GST_PAD_SINK, caps, NULL); + fail_unless (adj_caps != NULL); + fail_unless (gst_caps_get_size (adj_caps) == 1); + w_val = + gst_structure_get_value (gst_caps_get_structure (adj_caps, 0), "width"); + fail_unless (w_val != NULL); + fail_unless (GST_VALUE_HOLDS_INT_RANGE (w_val)); + fail_unless_equals_int (gst_value_get_int_range_min (w_val), 1000 - (1 + 3)); + fail_unless_equals_int (gst_value_get_int_range_max (w_val), 2000 - (1 + 3)); + h_val = + gst_structure_get_value (gst_caps_get_structure (adj_caps, 0), "height"); + fail_unless (h_val != NULL); + fail_unless (GST_VALUE_HOLDS_INT_RANGE (h_val)); + fail_unless_equals_int (gst_value_get_int_range_min (h_val), 3000 - (5 + 7)); + fail_unless_equals_int (gst_value_get_int_range_max (h_val), 4000 - (5 + 7)); + gst_caps_unref (adj_caps); + + /* ========= (3) range (adjustment at boundary) ========================== */ + + gst_structure_set (gst_caps_get_structure (caps, 0), + "width", GST_TYPE_INT_RANGE, 2, G_MAXINT, + "height", GST_TYPE_INT_RANGE, 2, G_MAXINT, NULL); + + /* sink => source, source must be bigger if we crop stuff off */ + adj_caps = klass->transform_caps (crop, GST_PAD_SRC, caps, NULL); + fail_unless (adj_caps != NULL); + fail_unless (gst_caps_get_size (adj_caps) == 1); + w_val = + gst_structure_get_value (gst_caps_get_structure (adj_caps, 0), "width"); + fail_unless (w_val != NULL); + fail_unless (GST_VALUE_HOLDS_INT_RANGE (w_val)); + fail_unless_equals_int (gst_value_get_int_range_min (w_val), 2 + (1 + 3)); + fail_unless_equals_int (gst_value_get_int_range_max (w_val), G_MAXINT); + h_val = + gst_structure_get_value (gst_caps_get_structure (adj_caps, 0), "height"); + fail_unless (h_val != NULL); + fail_unless (GST_VALUE_HOLDS_INT_RANGE (h_val)); + fail_unless_equals_int (gst_value_get_int_range_min (h_val), 2 + (5 + 7)); + fail_unless_equals_int (gst_value_get_int_range_max (h_val), G_MAXINT); + gst_caps_unref (adj_caps); + + /* source => sink becomes smaller */ + adj_caps = klass->transform_caps (crop, GST_PAD_SINK, caps, NULL); + fail_unless (adj_caps != NULL); + fail_unless (gst_caps_get_size (adj_caps) == 1); + w_val = + gst_structure_get_value (gst_caps_get_structure (adj_caps, 0), "width"); + fail_unless (w_val != NULL); + fail_unless (GST_VALUE_HOLDS_INT_RANGE (w_val)); + fail_unless_equals_int (gst_value_get_int_range_min (w_val), 1); + fail_unless_equals_int (gst_value_get_int_range_max (w_val), + G_MAXINT - (1 + 3)); + h_val = + gst_structure_get_value (gst_caps_get_structure (adj_caps, 0), "height"); + fail_unless (h_val != NULL); + fail_unless (GST_VALUE_HOLDS_INT_RANGE (h_val)); + fail_unless_equals_int (gst_value_get_int_range_min (h_val), 1); + fail_unless_equals_int (gst_value_get_int_range_max (h_val), + G_MAXINT - (5 + 7)); + gst_caps_unref (adj_caps); + + /* ========= (4) list of values ========================================== */ + + { + GValue list = { 0, }; + GValue ival = { 0, }; + + g_value_init (&ival, G_TYPE_INT); + g_value_init (&list, GST_TYPE_LIST); + g_value_set_int (&ival, 2); + gst_value_list_append_value (&list, &ival); + g_value_set_int (&ival, G_MAXINT); + gst_value_list_append_value (&list, &ival); + gst_structure_set_value (gst_caps_get_structure (caps, 0), "width", &list); + g_value_unset (&list); + g_value_unset (&ival); + + g_value_init (&ival, G_TYPE_INT); + g_value_init (&list, GST_TYPE_LIST); + g_value_set_int (&ival, 5); + gst_value_list_append_value (&list, &ival); + g_value_set_int (&ival, 1000); + gst_value_list_append_value (&list, &ival); + gst_structure_set_value (gst_caps_get_structure (caps, 0), "height", &list); + g_value_unset (&list); + g_value_unset (&ival); + } + + /* sink => source, source must be bigger if we crop stuff off */ + adj_caps = klass->transform_caps (crop, GST_PAD_SRC, caps, NULL); + fail_unless (adj_caps != NULL); + fail_unless (gst_caps_get_size (adj_caps) == 1); + w_val = + gst_structure_get_value (gst_caps_get_structure (adj_caps, 0), "width"); + fail_unless (w_val != NULL); + fail_unless (GST_VALUE_HOLDS_LIST (w_val)); + fail_unless_equals_int (notgst_value_list_get_nth_int (w_val, 0), + 2 + (1 + 3)); + fail_unless_equals_int (notgst_value_list_get_nth_int (w_val, 1), G_MAXINT); + h_val = + gst_structure_get_value (gst_caps_get_structure (adj_caps, 0), "height"); + fail_unless (h_val != NULL); + fail_unless (GST_VALUE_HOLDS_LIST (h_val)); + fail_unless_equals_int (notgst_value_list_get_nth_int (h_val, 0), + 5 + (5 + 7)); + fail_unless_equals_int (notgst_value_list_get_nth_int (h_val, 1), + 1000 + (5 + 7)); + gst_caps_unref (adj_caps); + + /* source => sink becomes smaller */ + adj_caps = klass->transform_caps (crop, GST_PAD_SINK, caps, NULL); + fail_unless (adj_caps != NULL); + fail_unless (gst_caps_get_size (adj_caps) == 1); + w_val = + gst_structure_get_value (gst_caps_get_structure (adj_caps, 0), "width"); + fail_unless (w_val != NULL); + fail_unless (GST_VALUE_HOLDS_LIST (w_val)); + fail_unless_equals_int (notgst_value_list_get_nth_int (w_val, 0), 1); + fail_unless_equals_int (notgst_value_list_get_nth_int (w_val, 1), + G_MAXINT - (1 + 3)); + h_val = + gst_structure_get_value (gst_caps_get_structure (adj_caps, 0), "height"); + fail_unless (h_val != NULL); + fail_unless (GST_VALUE_HOLDS_LIST (h_val)); + fail_unless_equals_int (notgst_value_list_get_nth_int (h_val, 0), 1); + fail_unless_equals_int (notgst_value_list_get_nth_int (h_val, 1), + 1000 - (5 + 7)); + gst_caps_unref (adj_caps); + + gst_caps_unref (caps); + videocrop_test_cropping_deinit_context (&ctx); +} + +GST_END_TEST; + +static Suite * +videocrop_suite (void) +{ + Suite *s = suite_create ("videocrop"); + TCase *tc_chain = tcase_create ("general"); + +#ifdef HAVE_VALGRIND + if (RUNNING_ON_VALGRIND) { + /* our tests take quite a long time, so increase + * timeout (~25 minutes on my 1.6GHz AMD K7) */ + tcase_set_timeout (tc_chain, 30 * 60); + } else +#endif + { + /* increase timeout, these tests take a long time (60 secs here) */ + tcase_set_timeout (tc_chain, 2 * 60); + } + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_crop_to_1x1); + tcase_add_test (tc_chain, test_caps_transform); + tcase_add_test (tc_chain, test_passthrough); + tcase_add_test (tc_chain, test_unit_sizes); + tcase_add_loop_test (tc_chain, test_cropping, 0, 25); + + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = videocrop_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/videofilter.c b/tests/check/elements/videofilter.c new file mode 100755 index 0000000..17b67f8 --- /dev/null +++ b/tests/check/elements/videofilter.c @@ -0,0 +1,281 @@ +/* GStreamer + * + * unit test for videofilter elements + * + * Copyright (C) <2006> Mark Nauwelaerts <manauw@skynet.be> + * + * 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 <unistd.h> +#include <stdarg.h> + +#include <gst/video/video.h> +#include <gst/check/gstcheck.h> + +gboolean have_eos = FALSE; + +/* 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 */ +GstPad *mysrcpad, *mysinkpad; + +#define VIDEO_CAPS_TEMPLATE_STRING \ + GST_VIDEO_CAPS_MAKE ("{ I420, AYUV, YUY2, UYVY, YVYU, xRGB }") + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (VIDEO_CAPS_TEMPLATE_STRING) + ); +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (VIDEO_CAPS_TEMPLATE_STRING) + ); + +/* takes over reference for outcaps */ +static GstElement * +setup_filter (const gchar * name, const gchar * prop, va_list var_args) +{ + GstElement *element; + + GST_DEBUG ("setup_element"); + element = gst_check_setup_element (name); + g_object_set_valist (G_OBJECT (element), prop, var_args); + mysrcpad = gst_check_setup_src_pad (element, &srctemplate); + gst_pad_set_active (mysrcpad, TRUE); + mysinkpad = gst_check_setup_sink_pad (element, &sinktemplate); + gst_pad_set_active (mysinkpad, TRUE); + + return element; +} + +static void +cleanup_filter (GstElement * filter) +{ + GST_DEBUG ("cleanup_element"); + + gst_check_teardown_src_pad (filter); + gst_check_teardown_sink_pad (filter); + gst_check_teardown_element (filter); +} + +static void +check_filter_caps (const gchar * name, GstEvent * event, GstCaps * caps, + gint size, gint num_buffers, const gchar * prop, va_list varargs) +{ + GstElement *filter; + GstBuffer *inbuffer, *outbuffer; + gint i; + GstSegment segment; + + filter = setup_filter (name, prop, varargs); + fail_unless (gst_element_set_state (filter, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + gst_check_setup_events (mysrcpad, filter, caps, GST_FORMAT_TIME); + + /* ensure segment (format) properly setup */ + gst_segment_init (&segment, GST_FORMAT_TIME); + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment))); + + if (event) + fail_unless (gst_pad_push_event (mysrcpad, event)); + + for (i = 0; i < num_buffers; ++i) { + inbuffer = gst_buffer_new_and_alloc (size); + /* makes valgrind's memcheck happier */ + gst_buffer_memset (inbuffer, 0, 0, size); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + } + + fail_unless (g_list_length (buffers) == num_buffers); + + /* clean up buffers */ + for (i = 0; i < num_buffers; ++i) { + outbuffer = GST_BUFFER (buffers->data); + fail_if (outbuffer == NULL); + + switch (i) { + case 0: + fail_unless (gst_buffer_get_size (outbuffer) == size); + /* no check on filter operation itself */ + break; + default: + break; + } + buffers = g_list_remove (buffers, outbuffer); + + ASSERT_BUFFER_REFCOUNT (outbuffer, "outbuffer", 1); + gst_buffer_unref (outbuffer); + outbuffer = NULL; + } + + cleanup_filter (filter); + g_list_free (buffers); + buffers = NULL; +} + +static void +check_filter_varargs (const gchar * name, GstEvent * event, gint num_buffers, + const gchar * prop, va_list varargs) +{ + static const struct + { + const int width, height; + } resolutions[] = { { + 384, 288}, { + 385, 289}, { + 385, 385}}; + gint i, n, r; + gint size; + GstCaps *allcaps, *templ = gst_caps_from_string (VIDEO_CAPS_TEMPLATE_STRING); + + allcaps = gst_caps_normalize (templ); + + n = gst_caps_get_size (allcaps); + + for (i = 0; i < n; i++) { + GstStructure *s = gst_caps_get_structure (allcaps, i); + GstCaps *caps = gst_caps_new_empty (); + + gst_caps_append_structure (caps, gst_structure_copy (s)); + + /* try various resolutions */ + for (r = 0; r < G_N_ELEMENTS (resolutions); ++r) { + GstVideoInfo info; + va_list args_cp; + + caps = gst_caps_make_writable (caps); + gst_caps_set_simple (caps, "width", G_TYPE_INT, resolutions[r].width, + "height", G_TYPE_INT, resolutions[r].height, + "framerate", GST_TYPE_FRACTION, 25, 1, NULL); + + GST_DEBUG ("Testing with caps: %" GST_PTR_FORMAT, caps); + gst_video_info_from_caps (&info, caps); + size = GST_VIDEO_INFO_SIZE (&info); + + if (event) + gst_event_ref (event); + + va_copy (args_cp, varargs); + check_filter_caps (name, event, caps, size, num_buffers, prop, args_cp); + va_end (args_cp); + } + + gst_caps_unref (caps); + } + + gst_caps_unref (allcaps); + if (event) + gst_event_unref (event); +} + +static void +check_filter (const gchar * name, gint num_buffers, const gchar * prop, ...) +{ + va_list varargs; + va_start (varargs, prop); + check_filter_varargs (name, NULL, num_buffers, prop, varargs); + va_end (varargs); +} + +static void +check_filter_with_event (const gchar * name, GstEvent * event, + gint num_buffers, const gchar * prop, ...) +{ + va_list varargs; + va_start (varargs, prop); + check_filter_varargs (name, event, num_buffers, prop, varargs); + va_end (varargs); +} + +GST_START_TEST (test_videobalance) +{ + check_filter ("videobalance", 2, NULL); + check_filter ("videobalance", 2, "saturation", 0.5, "hue", 0.8, NULL); +} + +GST_END_TEST; + + +GST_START_TEST (test_videoflip) +{ + GstEvent *event; + + /* these we can handle with the caps */ + check_filter ("videoflip", 2, "method", 0, NULL); + check_filter ("videoflip", 2, "method", 2, NULL); + check_filter ("videoflip", 2, "method", 4, NULL); + check_filter ("videoflip", 2, "method", 5, NULL); + + event = gst_event_new_tag (gst_tag_list_new_empty ()); + check_filter_with_event ("videoflip", event, 2, "method", 8, NULL); + + event = gst_event_new_tag (gst_tag_list_new (GST_TAG_IMAGE_ORIENTATION, + "rotate-180", NULL)); + check_filter_with_event ("videoflip", event, 2, "method", 8, NULL); + + event = gst_event_new_tag (gst_tag_list_new (GST_TAG_IMAGE_ORIENTATION, + "invalid", NULL)); + check_filter_with_event ("videoflip", event, 2, "method", 8, NULL); +} + +GST_END_TEST; + +GST_START_TEST (test_gamma) +{ + check_filter ("gamma", 2, NULL); + check_filter ("gamma", 2, "gamma", 2.0, NULL); +} + +GST_END_TEST; + + +static Suite * +videofilter_suite (void) +{ + Suite *s = suite_create ("videofilter"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_videobalance); + tcase_add_test (tc_chain, test_videoflip); + tcase_add_test (tc_chain, test_gamma); + + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = videofilter_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/videomixer.c b/tests/check/elements/videomixer.c new file mode 100755 index 0000000..e428332 --- /dev/null +++ b/tests/check/elements/videomixer.c @@ -0,0 +1,1063 @@ +/* GStreamer + * + * unit test for videmixer + * + * Copyright (C) <2005> Thomas Vander Stichele <thomas at apestaart dot org> + * Copyright (C) <2013> Thibault Saunier <thibault.saunier@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 + +#ifdef HAVE_VALGRIND +# include <valgrind/valgrind.h> +#endif + +#include <unistd.h> + +#include <gst/check/gstcheck.h> +#include <gst/check/gstconsistencychecker.h> +#include <gst/base/gstbasesrc.h> + +#define VIDEO_CAPS_STRING \ + "video/x-raw, " \ + "width = (int) 320, " \ + "height = (int) 240, " \ + "framerate = (fraction) 25/1 , " \ + "format = (string) I420" + +static GMainLoop *main_loop; + +/* make sure downstream gets a CAPS event before buffers are sent */ +GST_START_TEST (test_caps) +{ + GstElement *pipeline, *src, *videomixer, *sink; + GstStateChangeReturn state_res; + GstCaps *caps; + GstPad *pad; + + /* build pipeline */ + pipeline = gst_pipeline_new ("pipeline"); + + src = gst_element_factory_make ("videotestsrc", "src1"); + videomixer = gst_element_factory_make ("videomixer", "videomixer"); + sink = gst_element_factory_make ("fakesink", "sink"); + gst_bin_add_many (GST_BIN (pipeline), src, videomixer, sink, NULL); + + fail_unless (gst_element_link_many (src, videomixer, sink, NULL)); + + /* prepare playing */ + state_res = gst_element_set_state (pipeline, GST_STATE_PAUSED); + fail_unless_equals_int (state_res, GST_STATE_CHANGE_ASYNC); + + /* wait for preroll */ + state_res = gst_element_get_state (pipeline, NULL, NULL, GST_CLOCK_TIME_NONE); + fail_unless_equals_int (state_res, GST_STATE_CHANGE_SUCCESS); + + /* check caps on fakesink */ + pad = gst_element_get_static_pad (sink, "sink"); + caps = gst_pad_get_current_caps (pad); + fail_unless (caps != NULL); + gst_caps_unref (caps); + gst_object_unref (pad); + + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_object_unref (pipeline); +} + +GST_END_TEST; + +static void +message_received (GstBus * bus, GstMessage * message, GstPipeline * bin) +{ + GST_INFO ("bus message from \"%" GST_PTR_FORMAT "\": %" GST_PTR_FORMAT, + GST_MESSAGE_SRC (message), message); + + switch (message->type) { + case GST_MESSAGE_EOS: + g_main_loop_quit (main_loop); + break; + case GST_MESSAGE_WARNING:{ + GError *gerror; + gchar *debug; + + gst_message_parse_warning (message, &gerror, &debug); + gst_object_default_error (GST_MESSAGE_SRC (message), gerror, debug); + g_error_free (gerror); + g_free (debug); + break; + } + case GST_MESSAGE_ERROR:{ + GError *gerror; + gchar *debug; + + gst_message_parse_error (message, &gerror, &debug); + gst_object_default_error (GST_MESSAGE_SRC (message), gerror, debug); + g_error_free (gerror); + g_free (debug); + g_main_loop_quit (main_loop); + break; + } + default: + break; + } +} + + +static GstFormat format = GST_FORMAT_UNDEFINED; +static gint64 position = -1; + +static void +test_event_message_received (GstBus * bus, GstMessage * message, + GstPipeline * bin) +{ + GST_INFO ("bus message from \"%" GST_PTR_FORMAT "\": %" GST_PTR_FORMAT, + GST_MESSAGE_SRC (message), message); + + switch (message->type) { + case GST_MESSAGE_SEGMENT_DONE: + gst_message_parse_segment_done (message, &format, &position); + GST_INFO ("received segment_done : %" G_GINT64_FORMAT, position); + g_main_loop_quit (main_loop); + break; + default: + g_assert_not_reached (); + break; + } +} + + +GST_START_TEST (test_event) +{ + GstElement *bin, *src1, *src2, *videomixer, *sink; + GstBus *bus; + GstEvent *seek_event; + GstStateChangeReturn state_res; + gboolean res; + GstPad *srcpad, *sinkpad; + GstStreamConsistency *chk_1, *chk_2, *chk_3; + + GST_INFO ("preparing test"); + + /* build pipeline */ + bin = gst_pipeline_new ("pipeline"); + bus = gst_element_get_bus (bin); + gst_bus_add_signal_watch_full (bus, G_PRIORITY_HIGH); + + src1 = gst_element_factory_make ("videotestsrc", "src1"); + src2 = gst_element_factory_make ("videotestsrc", "src2"); + videomixer = gst_element_factory_make ("videomixer", "videomixer"); + sink = gst_element_factory_make ("fakesink", "sink"); + gst_bin_add_many (GST_BIN (bin), src1, src2, videomixer, sink, NULL); + + res = gst_element_link (src1, videomixer); + fail_unless (res == TRUE, NULL); + res = gst_element_link (src2, videomixer); + fail_unless (res == TRUE, NULL); + res = gst_element_link (videomixer, sink); + fail_unless (res == TRUE, NULL); + + srcpad = gst_element_get_static_pad (videomixer, "src"); + chk_3 = gst_consistency_checker_new (srcpad); + gst_object_unref (srcpad); + + /* create consistency checkers for the pads */ + srcpad = gst_element_get_static_pad (src1, "src"); + chk_1 = gst_consistency_checker_new (srcpad); + sinkpad = gst_pad_get_peer (srcpad); + gst_consistency_checker_add_pad (chk_3, sinkpad); + gst_object_unref (sinkpad); + gst_object_unref (srcpad); + + srcpad = gst_element_get_static_pad (src2, "src"); + chk_2 = gst_consistency_checker_new (srcpad); + sinkpad = gst_pad_get_peer (srcpad); + gst_consistency_checker_add_pad (chk_3, sinkpad); + gst_object_unref (sinkpad); + gst_object_unref (srcpad); + + seek_event = gst_event_new_seek (1.0, GST_FORMAT_TIME, + GST_SEEK_FLAG_SEGMENT | GST_SEEK_FLAG_FLUSH, + GST_SEEK_TYPE_SET, (GstClockTime) 0, + GST_SEEK_TYPE_SET, (GstClockTime) 2 * GST_SECOND); + + format = GST_FORMAT_UNDEFINED; + position = -1; + + main_loop = g_main_loop_new (NULL, FALSE); + g_signal_connect (bus, "message::segment-done", + (GCallback) test_event_message_received, bin); + g_signal_connect (bus, "message::error", (GCallback) message_received, bin); + g_signal_connect (bus, "message::warning", (GCallback) message_received, bin); + g_signal_connect (bus, "message::eos", (GCallback) message_received, bin); + + GST_INFO ("starting test"); + + /* prepare playing */ + state_res = gst_element_set_state (bin, GST_STATE_PAUSED); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + /* wait for completion */ + state_res = gst_element_get_state (bin, NULL, NULL, GST_CLOCK_TIME_NONE); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + res = gst_element_send_event (bin, seek_event); + fail_unless (res == TRUE, NULL); + + /* run pipeline */ + state_res = gst_element_set_state (bin, GST_STATE_PLAYING); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + GST_INFO ("running main loop"); + g_main_loop_run (main_loop); + + state_res = gst_element_set_state (bin, GST_STATE_NULL); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + ck_assert_int_eq (position, 2 * GST_SECOND); + + /* cleanup */ + g_main_loop_unref (main_loop); + gst_consistency_checker_free (chk_1); + gst_consistency_checker_free (chk_2); + gst_consistency_checker_free (chk_3); + gst_bus_remove_signal_watch (bus); + gst_object_unref (bus); + gst_object_unref (bin); +} + +GST_END_TEST; + +static guint play_count = 0; +static GstEvent *play_seek_event = NULL; + +static void +test_play_twice_message_received (GstBus * bus, GstMessage * message, + GstPipeline * bin) +{ + gboolean res; + GstStateChangeReturn state_res; + + GST_INFO ("bus message from \"%" GST_PTR_FORMAT "\": %" GST_PTR_FORMAT, + GST_MESSAGE_SRC (message), message); + + switch (message->type) { + case GST_MESSAGE_SEGMENT_DONE: + play_count++; + if (play_count == 1) { + state_res = gst_element_set_state (GST_ELEMENT (bin), GST_STATE_READY); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + /* prepare playing again */ + state_res = gst_element_set_state (GST_ELEMENT (bin), GST_STATE_PAUSED); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + /* wait for completion */ + state_res = + gst_element_get_state (GST_ELEMENT (bin), NULL, NULL, + GST_CLOCK_TIME_NONE); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + res = gst_element_send_event (GST_ELEMENT (bin), + gst_event_ref (play_seek_event)); + fail_unless (res == TRUE, NULL); + + state_res = + gst_element_set_state (GST_ELEMENT (bin), GST_STATE_PLAYING); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + } else { + g_main_loop_quit (main_loop); + } + break; + default: + g_assert_not_reached (); + break; + } +} + + +GST_START_TEST (test_play_twice) +{ + GstElement *bin, *src1, *src2, *videomixer, *sink; + GstBus *bus; + gboolean res; + GstStateChangeReturn state_res; + GstPad *srcpad; + GstStreamConsistency *consist; + + GST_INFO ("preparing test"); + + /* build pipeline */ + bin = gst_pipeline_new ("pipeline"); + bus = gst_element_get_bus (bin); + gst_bus_add_signal_watch_full (bus, G_PRIORITY_HIGH); + + src1 = gst_element_factory_make ("videotestsrc", "src1"); + src2 = gst_element_factory_make ("videotestsrc", "src2"); + videomixer = gst_element_factory_make ("videomixer", "videomixer"); + sink = gst_element_factory_make ("fakesink", "sink"); + gst_bin_add_many (GST_BIN (bin), src1, src2, videomixer, sink, NULL); + + res = gst_element_link (src1, videomixer); + fail_unless (res == TRUE, NULL); + res = gst_element_link (src2, videomixer); + fail_unless (res == TRUE, NULL); + res = gst_element_link (videomixer, sink); + fail_unless (res == TRUE, NULL); + + srcpad = gst_element_get_static_pad (videomixer, "src"); + consist = gst_consistency_checker_new (srcpad); + gst_object_unref (srcpad); + + play_seek_event = gst_event_new_seek (1.0, GST_FORMAT_TIME, + GST_SEEK_FLAG_SEGMENT | GST_SEEK_FLAG_FLUSH, + GST_SEEK_TYPE_SET, (GstClockTime) 0, + GST_SEEK_TYPE_SET, (GstClockTime) 2 * GST_SECOND); + + play_count = 0; + + main_loop = g_main_loop_new (NULL, FALSE); + g_signal_connect (bus, "message::segment-done", + (GCallback) test_play_twice_message_received, bin); + g_signal_connect (bus, "message::error", (GCallback) message_received, bin); + g_signal_connect (bus, "message::warning", (GCallback) message_received, bin); + g_signal_connect (bus, "message::eos", (GCallback) message_received, bin); + + GST_INFO ("starting test"); + + /* prepare playing */ + state_res = gst_element_set_state (bin, GST_STATE_PAUSED); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + /* wait for completion */ + state_res = + gst_element_get_state (GST_ELEMENT (bin), NULL, NULL, + GST_CLOCK_TIME_NONE); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + res = gst_element_send_event (bin, gst_event_ref (play_seek_event)); + fail_unless (res == TRUE, NULL); + + GST_INFO ("seeked"); + + /* run pipeline */ + state_res = gst_element_set_state (bin, GST_STATE_PLAYING); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + g_main_loop_run (main_loop); + + state_res = gst_element_set_state (bin, GST_STATE_NULL); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + ck_assert_int_eq (play_count, 2); + + /* cleanup */ + g_main_loop_unref (main_loop); + gst_consistency_checker_free (consist); + gst_event_ref (play_seek_event); + gst_bus_remove_signal_watch (bus); + gst_object_unref (bus); + gst_object_unref (bin); +} + +GST_END_TEST; + +GST_START_TEST (test_play_twice_then_add_and_play_again) +{ + GstElement *bin, *src1, *src2, *src3, *videomixer, *sink; + GstBus *bus; + gboolean res; + GstStateChangeReturn state_res; + gint i; + GstPad *srcpad; + GstStreamConsistency *consist; + + GST_INFO ("preparing test"); + + /* build pipeline */ + bin = gst_pipeline_new ("pipeline"); + bus = gst_element_get_bus (bin); + gst_bus_add_signal_watch_full (bus, G_PRIORITY_HIGH); + + src1 = gst_element_factory_make ("videotestsrc", "src1"); + src2 = gst_element_factory_make ("videotestsrc", "src2"); + videomixer = gst_element_factory_make ("videomixer", "videomixer"); + sink = gst_element_factory_make ("fakesink", "sink"); + gst_bin_add_many (GST_BIN (bin), src1, src2, videomixer, sink, NULL); + + srcpad = gst_element_get_static_pad (videomixer, "src"); + consist = gst_consistency_checker_new (srcpad); + gst_object_unref (srcpad); + + res = gst_element_link (src1, videomixer); + fail_unless (res == TRUE, NULL); + res = gst_element_link (src2, videomixer); + fail_unless (res == TRUE, NULL); + res = gst_element_link (videomixer, sink); + fail_unless (res == TRUE, NULL); + + play_seek_event = gst_event_new_seek (1.0, GST_FORMAT_TIME, + GST_SEEK_FLAG_SEGMENT | GST_SEEK_FLAG_FLUSH, + GST_SEEK_TYPE_SET, (GstClockTime) 0, + GST_SEEK_TYPE_SET, (GstClockTime) 2 * GST_SECOND); + + main_loop = g_main_loop_new (NULL, FALSE); + g_signal_connect (bus, "message::segment-done", + (GCallback) test_play_twice_message_received, bin); + g_signal_connect (bus, "message::error", (GCallback) message_received, bin); + g_signal_connect (bus, "message::warning", (GCallback) message_received, bin); + g_signal_connect (bus, "message::eos", (GCallback) message_received, bin); + + /* run it twice */ + for (i = 0; i < 2; i++) { + play_count = 0; + + GST_INFO ("starting test-loop %d", i); + + /* prepare playing */ + state_res = gst_element_set_state (bin, GST_STATE_PAUSED); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + /* wait for completion */ + state_res = + gst_element_get_state (GST_ELEMENT (bin), NULL, NULL, + GST_CLOCK_TIME_NONE); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + res = gst_element_send_event (bin, gst_event_ref (play_seek_event)); + fail_unless (res == TRUE, NULL); + + GST_INFO ("seeked"); + + /* run pipeline */ + state_res = gst_element_set_state (bin, GST_STATE_PLAYING); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + g_main_loop_run (main_loop); + + state_res = gst_element_set_state (bin, GST_STATE_READY); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + ck_assert_int_eq (play_count, 2); + + /* plug another source */ + if (i == 0) { + src3 = gst_element_factory_make ("videotestsrc", "src3"); + gst_bin_add (GST_BIN (bin), src3); + + res = gst_element_link (src3, videomixer); + fail_unless (res == TRUE, NULL); + } + + gst_consistency_checker_reset (consist); + } + + state_res = gst_element_set_state (bin, GST_STATE_NULL); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + /* cleanup */ + g_main_loop_unref (main_loop); + gst_event_ref (play_seek_event); + gst_consistency_checker_free (consist); + gst_bus_remove_signal_watch (bus); + gst_object_unref (bus); + gst_object_unref (bin); +} + +GST_END_TEST; + +/* check if adding pads work as expected */ +GST_START_TEST (test_add_pad) +{ + GstElement *bin, *src1, *src2, *videomixer, *sink; + GstBus *bus; + GstPad *srcpad; + gboolean res; + GstStateChangeReturn state_res; + + GST_INFO ("preparing test"); + + /* build pipeline */ + bin = gst_pipeline_new ("pipeline"); + bus = gst_element_get_bus (bin); + gst_bus_add_signal_watch_full (bus, G_PRIORITY_HIGH); + + src1 = gst_element_factory_make ("videotestsrc", "src1"); + g_object_set (src1, "num-buffers", 4, NULL); + src2 = gst_element_factory_make ("videotestsrc", "src2"); + /* one buffer less, we connect with 1 buffer of delay */ + g_object_set (src2, "num-buffers", 3, NULL); + videomixer = gst_element_factory_make ("videomixer", "videomixer"); + sink = gst_element_factory_make ("fakesink", "sink"); + gst_bin_add_many (GST_BIN (bin), src1, videomixer, sink, NULL); + + res = gst_element_link (src1, videomixer); + fail_unless (res == TRUE, NULL); + res = gst_element_link (videomixer, sink); + fail_unless (res == TRUE, NULL); + + srcpad = gst_element_get_static_pad (videomixer, "src"); + gst_object_unref (srcpad); + + main_loop = g_main_loop_new (NULL, FALSE); + g_signal_connect (bus, "message::segment-done", (GCallback) message_received, + bin); + g_signal_connect (bus, "message::error", (GCallback) message_received, bin); + g_signal_connect (bus, "message::warning", (GCallback) message_received, bin); + g_signal_connect (bus, "message::eos", (GCallback) message_received, bin); + + GST_INFO ("starting test"); + + /* prepare playing */ + state_res = gst_element_set_state (bin, GST_STATE_PAUSED); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + /* wait for completion */ + state_res = + gst_element_get_state (GST_ELEMENT (bin), NULL, NULL, + GST_CLOCK_TIME_NONE); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + /* add other element */ + gst_bin_add_many (GST_BIN (bin), src2, NULL); + + /* now link the second element */ + res = gst_element_link (src2, videomixer); + fail_unless (res == TRUE, NULL); + + /* set to PAUSED as well */ + state_res = gst_element_set_state (src2, GST_STATE_PAUSED); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + /* now play all */ + state_res = gst_element_set_state (bin, GST_STATE_PLAYING); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + g_main_loop_run (main_loop); + + state_res = gst_element_set_state (bin, GST_STATE_NULL); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + /* cleanup */ + g_main_loop_unref (main_loop); + gst_bus_remove_signal_watch (bus); + gst_object_unref (bus); + gst_object_unref (bin); +} + +GST_END_TEST; + +/* check if removing pads work as expected */ +GST_START_TEST (test_remove_pad) +{ + GstElement *bin, *src, *videomixer, *sink; + GstBus *bus; + GstPad *pad, *srcpad; + gboolean res; + GstStateChangeReturn state_res; + + GST_INFO ("preparing test"); + + /* build pipeline */ + bin = gst_pipeline_new ("pipeline"); + bus = gst_element_get_bus (bin); + gst_bus_add_signal_watch_full (bus, G_PRIORITY_HIGH); + + src = gst_element_factory_make ("videotestsrc", "src"); + g_object_set (src, "num-buffers", 4, NULL); + videomixer = gst_element_factory_make ("videomixer", "videomixer"); + sink = gst_element_factory_make ("fakesink", "sink"); + gst_bin_add_many (GST_BIN (bin), src, videomixer, sink, NULL); + + res = gst_element_link (src, videomixer); + fail_unless (res == TRUE, NULL); + res = gst_element_link (videomixer, sink); + fail_unless (res == TRUE, NULL); + + /* create an unconnected sinkpad in videomixer */ + pad = gst_element_get_request_pad (videomixer, "sink_%u"); + fail_if (pad == NULL, NULL); + + srcpad = gst_element_get_static_pad (videomixer, "src"); + gst_object_unref (srcpad); + + main_loop = g_main_loop_new (NULL, FALSE); + g_signal_connect (bus, "message::segment-done", (GCallback) message_received, + bin); + g_signal_connect (bus, "message::error", (GCallback) message_received, bin); + g_signal_connect (bus, "message::warning", (GCallback) message_received, bin); + g_signal_connect (bus, "message::eos", (GCallback) message_received, bin); + + GST_INFO ("starting test"); + + /* prepare playing, this will not preroll as videomixer is waiting + * on the unconnected sinkpad. */ + state_res = gst_element_set_state (bin, GST_STATE_PAUSED); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + /* wait for completion for one second, will return ASYNC */ + state_res = gst_element_get_state (GST_ELEMENT (bin), NULL, NULL, GST_SECOND); + ck_assert_int_eq (state_res, GST_STATE_CHANGE_ASYNC); + + /* get rid of the pad now, videomixer should stop waiting on it and + * continue the preroll */ + gst_element_release_request_pad (videomixer, pad); + gst_object_unref (pad); + + /* wait for completion, should work now */ + state_res = + gst_element_get_state (GST_ELEMENT (bin), NULL, NULL, + GST_CLOCK_TIME_NONE); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + /* now play all */ + state_res = gst_element_set_state (bin, GST_STATE_PLAYING); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + g_main_loop_run (main_loop); + + state_res = gst_element_set_state (bin, GST_STATE_NULL); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + /* cleanup */ + g_main_loop_unref (main_loop); + gst_bus_remove_signal_watch (bus); + gst_object_unref (G_OBJECT (bus)); + gst_object_unref (G_OBJECT (bin)); +} + +GST_END_TEST; + + +static GstBuffer *handoff_buffer = NULL; +static void +handoff_buffer_cb (GstElement * fakesink, GstBuffer * buffer, GstPad * pad, + gpointer user_data) +{ + GST_DEBUG ("got buffer %p", buffer); + gst_buffer_replace (&handoff_buffer, buffer); +} + +/* check if clipping works as expected */ +GST_START_TEST (test_clip) +{ + GstSegment segment; + GstElement *bin, *videomixer, *sink; + GstBus *bus; + GstPad *sinkpad; + gboolean res; + GstStateChangeReturn state_res; + GstFlowReturn ret; + GstEvent *event; + GstBuffer *buffer; + GstCaps *caps; + + GST_INFO ("preparing test"); + + /* build pipeline */ + bin = gst_pipeline_new ("pipeline"); + bus = gst_element_get_bus (bin); + gst_bus_add_signal_watch_full (bus, G_PRIORITY_HIGH); + + g_signal_connect (bus, "message::error", (GCallback) message_received, bin); + g_signal_connect (bus, "message::warning", (GCallback) message_received, bin); + g_signal_connect (bus, "message::eos", (GCallback) message_received, bin); + + /* just an videomixer and a fakesink */ + videomixer = gst_element_factory_make ("videomixer", "videomixer"); + sink = gst_element_factory_make ("fakesink", "sink"); + g_object_set (sink, "signal-handoffs", TRUE, NULL); + g_signal_connect (sink, "handoff", (GCallback) handoff_buffer_cb, NULL); + gst_bin_add_many (GST_BIN (bin), videomixer, sink, NULL); + + res = gst_element_link (videomixer, sink); + fail_unless (res == TRUE, NULL); + + /* set to playing */ + state_res = gst_element_set_state (bin, GST_STATE_PLAYING); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + /* create an unconnected sinkpad in videomixer, should also automatically activate + * the pad */ + sinkpad = gst_element_get_request_pad (videomixer, "sink_%u"); + fail_if (sinkpad == NULL, NULL); + + gst_pad_send_event (sinkpad, gst_event_new_stream_start ("test")); + + caps = gst_caps_from_string (VIDEO_CAPS_STRING); + + gst_pad_set_caps (sinkpad, caps); + gst_caps_unref (caps); + + /* send segment to videomixer */ + gst_segment_init (&segment, GST_FORMAT_TIME); + segment.start = GST_SECOND; + segment.stop = 2 * GST_SECOND; + segment.time = 0; + event = gst_event_new_segment (&segment); + gst_pad_send_event (sinkpad, event); + + /* should be clipped and ok */ + buffer = gst_buffer_new_and_alloc (115200); + GST_BUFFER_TIMESTAMP (buffer) = 0; + GST_BUFFER_DURATION (buffer) = 250 * GST_MSECOND; + GST_DEBUG ("pushing buffer %p", buffer); + ret = gst_pad_chain (sinkpad, buffer); + ck_assert_int_eq (ret, GST_FLOW_OK); + fail_unless (handoff_buffer == NULL); + + /* should be partially clipped */ + buffer = gst_buffer_new_and_alloc (115200); + GST_BUFFER_TIMESTAMP (buffer) = 900 * GST_MSECOND; + GST_BUFFER_DURATION (buffer) = 250 * GST_MSECOND; + GST_DEBUG ("pushing buffer %p", buffer); + ret = gst_pad_chain (sinkpad, buffer); + ck_assert_int_eq (ret, GST_FLOW_OK); + fail_unless (handoff_buffer != NULL); + gst_buffer_replace (&handoff_buffer, NULL); + + /* should not be clipped */ + buffer = gst_buffer_new_and_alloc (115200); + GST_BUFFER_TIMESTAMP (buffer) = 1 * GST_SECOND; + GST_BUFFER_DURATION (buffer) = 250 * GST_MSECOND; + GST_DEBUG ("pushing buffer %p", buffer); + ret = gst_pad_chain (sinkpad, buffer); + ck_assert_int_eq (ret, GST_FLOW_OK); + fail_unless (handoff_buffer != NULL); + gst_buffer_replace (&handoff_buffer, NULL); + + /* should be clipped and ok */ + buffer = gst_buffer_new_and_alloc (115200); + GST_BUFFER_TIMESTAMP (buffer) = 2 * GST_SECOND; + GST_BUFFER_DURATION (buffer) = 250 * GST_MSECOND; + GST_DEBUG ("pushing buffer %p", buffer); + ret = gst_pad_chain (sinkpad, buffer); + ck_assert_int_eq (ret, GST_FLOW_OK); + fail_unless (handoff_buffer == NULL); + + gst_object_unref (sinkpad); + gst_element_set_state (bin, GST_STATE_NULL); + gst_bus_remove_signal_watch (bus); + gst_object_unref (bus); + gst_object_unref (bin); +} + +GST_END_TEST; + +GST_START_TEST (test_duration_is_max) +{ + GstElement *bin, *src[3], *videomixer, *sink; + GstStateChangeReturn state_res; + GstFormat format = GST_FORMAT_TIME; + gboolean res; + gint64 duration; + + GST_INFO ("preparing test"); + + /* build pipeline */ + bin = gst_pipeline_new ("pipeline"); + + /* 3 sources, an videomixer and a fakesink */ + src[0] = gst_element_factory_make ("videotestsrc", NULL); + src[1] = gst_element_factory_make ("videotestsrc", NULL); + src[2] = gst_element_factory_make ("videotestsrc", NULL); + videomixer = gst_element_factory_make ("videomixer", "videomixer"); + sink = gst_element_factory_make ("fakesink", "sink"); + gst_bin_add_many (GST_BIN (bin), src[0], src[1], src[2], videomixer, sink, + NULL); + + gst_element_link (src[0], videomixer); + gst_element_link (src[1], videomixer); + gst_element_link (src[2], videomixer); + gst_element_link (videomixer, sink); + + /* irks, duration is reset on basesrc */ + state_res = gst_element_set_state (bin, GST_STATE_PAUSED); + fail_unless (state_res != GST_STATE_CHANGE_FAILURE, NULL); + + /* set durations on src */ + GST_BASE_SRC (src[0])->segment.duration = 1000; + GST_BASE_SRC (src[1])->segment.duration = 3000; + GST_BASE_SRC (src[2])->segment.duration = 2000; + + /* set to playing */ + state_res = gst_element_set_state (bin, GST_STATE_PLAYING); + fail_unless (state_res != GST_STATE_CHANGE_FAILURE, NULL); + + /* wait for completion */ + state_res = + gst_element_get_state (GST_ELEMENT (bin), NULL, NULL, + GST_CLOCK_TIME_NONE); + fail_unless (state_res != GST_STATE_CHANGE_FAILURE, NULL); + + res = gst_element_query_duration (GST_ELEMENT (bin), format, &duration); + fail_unless (res, NULL); + + ck_assert_int_eq (duration, 3000); + + gst_element_set_state (bin, GST_STATE_NULL); + gst_object_unref (bin); +} + +GST_END_TEST; + +GST_START_TEST (test_duration_unknown_overrides) +{ + GstElement *bin, *src[3], *videomixer, *sink; + GstStateChangeReturn state_res; + GstFormat format = GST_FORMAT_TIME; + gboolean res; + gint64 duration; + + GST_INFO ("preparing test"); + + /* build pipeline */ + bin = gst_pipeline_new ("pipeline"); + + /* 3 sources, an videomixer and a fakesink */ + src[0] = gst_element_factory_make ("videotestsrc", NULL); + src[1] = gst_element_factory_make ("videotestsrc", NULL); + src[2] = gst_element_factory_make ("videotestsrc", NULL); + videomixer = gst_element_factory_make ("videomixer", "videomixer"); + sink = gst_element_factory_make ("fakesink", "sink"); + gst_bin_add_many (GST_BIN (bin), src[0], src[1], src[2], videomixer, sink, + NULL); + + gst_element_link (src[0], videomixer); + gst_element_link (src[1], videomixer); + gst_element_link (src[2], videomixer); + gst_element_link (videomixer, sink); + + /* irks, duration is reset on basesrc */ + state_res = gst_element_set_state (bin, GST_STATE_PAUSED); + fail_unless (state_res != GST_STATE_CHANGE_FAILURE, NULL); + + /* set durations on src */ + GST_BASE_SRC (src[0])->segment.duration = GST_CLOCK_TIME_NONE; + GST_BASE_SRC (src[1])->segment.duration = 3000; + GST_BASE_SRC (src[2])->segment.duration = 2000; + + /* set to playing */ + state_res = gst_element_set_state (bin, GST_STATE_PLAYING); + fail_unless (state_res != GST_STATE_CHANGE_FAILURE, NULL); + + /* wait for completion */ + state_res = + gst_element_get_state (GST_ELEMENT (bin), NULL, NULL, + GST_CLOCK_TIME_NONE); + fail_unless (state_res != GST_STATE_CHANGE_FAILURE, NULL); + + res = gst_element_query_duration (GST_ELEMENT (bin), format, &duration); + fail_unless (res, NULL); + + ck_assert_int_eq (duration, GST_CLOCK_TIME_NONE); + + gst_element_set_state (bin, GST_STATE_NULL); + gst_object_unref (bin); +} + +GST_END_TEST; + + +static gboolean looped = FALSE; + +static void +loop_segment_done (GstBus * bus, GstMessage * message, GstElement * bin) +{ + GST_INFO ("bus message from \"%" GST_PTR_FORMAT "\": %" GST_PTR_FORMAT, + GST_MESSAGE_SRC (message), message); + + if (looped) { + g_main_loop_quit (main_loop); + } else { + GstEvent *seek_event; + gboolean res; + + seek_event = gst_event_new_seek (1.0, GST_FORMAT_TIME, + GST_SEEK_FLAG_SEGMENT, + GST_SEEK_TYPE_SET, (GstClockTime) 0, + GST_SEEK_TYPE_SET, (GstClockTime) 1 * GST_SECOND); + + res = gst_element_send_event (bin, seek_event); + fail_unless (res == TRUE, NULL); + looped = TRUE; + } +} + +GST_START_TEST (test_loop) +{ + GstElement *bin, *src1, *src2, *videomixer, *sink; + GstBus *bus; + GstEvent *seek_event; + GstStateChangeReturn state_res; + gboolean res; + + GST_INFO ("preparing test"); + + /* build pipeline */ + bin = gst_pipeline_new ("pipeline"); + bus = gst_element_get_bus (bin); + gst_bus_add_signal_watch_full (bus, G_PRIORITY_HIGH); + + src1 = gst_element_factory_make ("videotestsrc", "src1"); + src2 = gst_element_factory_make ("videotestsrc", "src2"); + videomixer = gst_element_factory_make ("videomixer", "videomixer"); + sink = gst_element_factory_make ("fakesink", "sink"); + gst_bin_add_many (GST_BIN (bin), src1, src2, videomixer, sink, NULL); + + res = gst_element_link (src1, videomixer); + fail_unless (res == TRUE, NULL); + res = gst_element_link (src2, videomixer); + fail_unless (res == TRUE, NULL); + res = gst_element_link (videomixer, sink); + fail_unless (res == TRUE, NULL); + + seek_event = gst_event_new_seek (1.0, GST_FORMAT_TIME, + GST_SEEK_FLAG_SEGMENT | GST_SEEK_FLAG_FLUSH, + GST_SEEK_TYPE_SET, (GstClockTime) 0, GST_SEEK_TYPE_SET, + (GstClockTime) 2 * GST_SECOND); + + main_loop = g_main_loop_new (NULL, FALSE); + g_signal_connect (bus, "message::segment-done", + (GCallback) loop_segment_done, bin); + g_signal_connect (bus, "message::error", (GCallback) message_received, bin); + g_signal_connect (bus, "message::warning", (GCallback) message_received, bin); + g_signal_connect (bus, "message::eos", (GCallback) message_received, bin); + + GST_INFO ("starting test"); + + /* prepare playing */ + state_res = gst_element_set_state (bin, GST_STATE_PAUSED); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + /* wait for completion */ + state_res = + gst_element_get_state (GST_ELEMENT (bin), NULL, NULL, + GST_CLOCK_TIME_NONE); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + res = gst_element_send_event (bin, seek_event); + fail_unless (res == TRUE, NULL); + + /* run pipeline */ + state_res = gst_element_set_state (bin, GST_STATE_PLAYING); + ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE); + + GST_INFO ("running main loop"); + g_main_loop_run (main_loop); + + state_res = gst_element_set_state (bin, GST_STATE_NULL); + + /* cleanup */ + g_main_loop_unref (main_loop); + gst_bus_remove_signal_watch (bus); + gst_object_unref (bus); + gst_object_unref (bin); +} + +GST_END_TEST; + +GST_START_TEST (test_flush_start_flush_stop) +{ + GstPadTemplate *sink_template; + GstPad *tmppad, *sinkpad1, *sinkpad2, *videomixer_src; + GstElement *pipeline, *src1, *src2, *videomixer, *sink; + + GST_INFO ("preparing test"); + + /* build pipeline */ + pipeline = gst_pipeline_new ("pipeline"); + src1 = gst_element_factory_make ("videotestsrc", "src1"); + src2 = gst_element_factory_make ("videotestsrc", "src2"); + videomixer = gst_element_factory_make ("videomixer", "videomixer"); + sink = gst_element_factory_make ("fakesink", "sink"); + gst_bin_add_many (GST_BIN (pipeline), src1, src2, videomixer, sink, NULL); + + sink_template = + gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (videomixer), + "sink_%u"); + fail_unless (GST_IS_PAD_TEMPLATE (sink_template)); + sinkpad1 = gst_element_request_pad (videomixer, sink_template, NULL, NULL); + tmppad = gst_element_get_static_pad (src1, "src"); + gst_pad_link (tmppad, sinkpad1); + gst_object_unref (tmppad); + + sinkpad2 = gst_element_request_pad (videomixer, sink_template, NULL, NULL); + tmppad = gst_element_get_static_pad (src2, "src"); + gst_pad_link (tmppad, sinkpad2); + gst_object_unref (tmppad); + + gst_element_link (videomixer, sink); + + gst_element_set_state (pipeline, GST_STATE_PLAYING); + fail_unless (gst_element_get_state (pipeline, NULL, NULL, + GST_CLOCK_TIME_NONE) == GST_STATE_CHANGE_SUCCESS); + + videomixer_src = gst_element_get_static_pad (videomixer, "src"); + fail_if (GST_PAD_IS_FLUSHING (videomixer_src)); + gst_pad_send_event (sinkpad1, gst_event_new_flush_start ()); + fail_unless (GST_PAD_IS_FLUSHING (videomixer_src)); + gst_pad_send_event (sinkpad1, gst_event_new_flush_stop (TRUE)); + fail_if (GST_PAD_IS_FLUSHING (videomixer_src)); + gst_object_unref (videomixer_src); + + /* cleanup */ + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_object_unref (sinkpad1); + gst_object_unref (sinkpad2); + gst_object_unref (pipeline); +} + +GST_END_TEST; + + +static Suite * +videomixer_suite (void) +{ + Suite *s = suite_create ("videomixer"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_caps); + tcase_add_test (tc_chain, test_event); + tcase_add_test (tc_chain, test_play_twice); + tcase_add_test (tc_chain, test_play_twice_then_add_and_play_again); + tcase_add_test (tc_chain, test_add_pad); + tcase_add_test (tc_chain, test_remove_pad); + tcase_add_test (tc_chain, test_clip); + tcase_add_test (tc_chain, test_duration_is_max); + tcase_add_test (tc_chain, test_duration_unknown_overrides); + tcase_add_test (tc_chain, test_loop); + tcase_add_test (tc_chain, test_flush_start_flush_stop); + + /* Use a longer timeout */ +#ifdef HAVE_VALGRIND + if (RUNNING_ON_VALGRIND) { + tcase_set_timeout (tc_chain, 5 * 60); + } else +#endif + { + /* this is shorter than the default 60 seconds?! (tpm) */ + /* tcase_set_timeout (tc_chain, 6); */ + } + + return s; +} + +GST_CHECK_MAIN (videomixer); diff --git a/tests/check/elements/vp8dec.c b/tests/check/elements/vp8dec.c new file mode 100644 index 0000000..786809e --- /dev/null +++ b/tests/check/elements/vp8dec.c @@ -0,0 +1,178 @@ +/* GStreamer + * + * Copyright (c) 2010 Sebastian Dröge <sebastian.droege@collabora.co.uk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <gst/check/gstcheck.h> + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-raw, " + "format = (string) I420, " + "width = (int) [1, MAX], " + "height = (int) [1, MAX], " "framerate = (fraction) [0, MAX]")); + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-raw, " + "format = (string) I420, " + "width = (int) [1, MAX], " + "height = (int) [1, MAX], " "framerate = (fraction) [0, MAX]")); + +static GstPad *sinkpad, *srcpad; + +static GstElement * +setup_vp8dec (const gchar * src_caps_str) +{ + GstElement *bin; + GstElement *vp8enc, *vp8dec; + GstCaps *srccaps = NULL; + GstBus *bus; + GstPad *ghostpad, *targetpad; + + if (src_caps_str) { + srccaps = gst_caps_from_string (src_caps_str); + fail_unless (srccaps != NULL); + } + + bin = gst_bin_new ("bin"); + + vp8enc = gst_check_setup_element ("vp8enc"); + fail_unless (vp8enc != NULL); + vp8dec = gst_check_setup_element ("vp8dec"); + fail_unless (vp8dec != NULL); + + gst_bin_add_many (GST_BIN (bin), vp8enc, vp8dec, NULL); + fail_unless (gst_element_link_pads (vp8enc, "src", vp8dec, "sink")); + + targetpad = gst_element_get_static_pad (vp8enc, "sink"); + fail_unless (targetpad != NULL); + ghostpad = gst_ghost_pad_new ("sink", targetpad); + fail_unless (ghostpad != NULL); + gst_element_add_pad (bin, ghostpad); + gst_object_unref (targetpad); + + targetpad = gst_element_get_static_pad (vp8dec, "src"); + fail_unless (targetpad != NULL); + ghostpad = gst_ghost_pad_new ("src", targetpad); + fail_unless (ghostpad != NULL); + gst_element_add_pad (bin, ghostpad); + gst_object_unref (targetpad); + + srcpad = gst_check_setup_src_pad (bin, &srctemplate); + sinkpad = gst_check_setup_sink_pad (bin, &sinktemplate); + gst_pad_set_active (srcpad, TRUE); + gst_pad_set_active (sinkpad, TRUE); + gst_check_setup_events (srcpad, bin, srccaps, GST_FORMAT_TIME); + + bus = gst_bus_new (); + gst_element_set_bus (bin, bus); + + fail_unless (gst_element_set_state (bin, + GST_STATE_PLAYING) != GST_STATE_CHANGE_FAILURE, + "could not set to playing"); + + if (srccaps) + gst_caps_unref (srccaps); + + buffers = NULL; + return bin; +} + +static void +cleanup_vp8dec (GstElement * bin) +{ + GstBus *bus; + + /* Free parsed buffers */ + gst_check_drop_buffers (); + + bus = GST_ELEMENT_BUS (bin); + gst_bus_set_flushing (bus, TRUE); + gst_object_unref (bus); + + gst_pad_set_active (srcpad, FALSE); + gst_pad_set_active (sinkpad, FALSE); + + gst_check_teardown_src_pad (bin); + gst_check_teardown_sink_pad (bin); + gst_check_teardown_element (bin); +} + +GST_START_TEST (test_decode_simple) +{ + GstElement *bin; + GstBuffer *buffer; + gint i; + GList *l; + GstSegment seg; + + bin = + setup_vp8dec + ("video/x-raw,format=(string)I420,width=(int)320,height=(int)240,framerate=(fraction)25/1"); + + gst_segment_init (&seg, GST_FORMAT_TIME); + seg.stop = gst_util_uint64_scale (20, GST_SECOND, 25); + fail_unless (gst_pad_push_event (srcpad, gst_event_new_segment (&seg))); + + buffer = gst_buffer_new_and_alloc (320 * 240 + 2 * 160 * 120); + gst_buffer_memset (buffer, 0, 0, -1); + + for (i = 0; i < 20; i++) { + GST_BUFFER_TIMESTAMP (buffer) = gst_util_uint64_scale (i, GST_SECOND, 25); + GST_BUFFER_DURATION (buffer) = gst_util_uint64_scale (1, GST_SECOND, 25); + fail_unless (gst_pad_push (srcpad, gst_buffer_ref (buffer)) == GST_FLOW_OK); + } + + gst_buffer_unref (buffer); + + fail_unless (gst_pad_push_event (srcpad, gst_event_new_eos ())); + + /* All buffers must be there now */ + fail_unless_equals_int (g_list_length (buffers), 20); + + for (l = buffers, i = 0; l; l = l->next, i++) { + buffer = l->data; + + fail_unless_equals_uint64 (GST_BUFFER_TIMESTAMP (buffer), + gst_util_uint64_scale (i, GST_SECOND, 25)); + fail_unless_equals_uint64 (GST_BUFFER_DURATION (buffer), + gst_util_uint64_scale (1, GST_SECOND, 25)); + } + + cleanup_vp8dec (bin); +} + +GST_END_TEST; + +static Suite * +vp8dec_suite (void) +{ + Suite *s = suite_create ("vp8dec"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_decode_simple); + + return s; +} + +GST_CHECK_MAIN (vp8dec); diff --git a/tests/check/elements/vp8enc.c b/tests/check/elements/vp8enc.c new file mode 100644 index 0000000..86b1d92 --- /dev/null +++ b/tests/check/elements/vp8enc.c @@ -0,0 +1,168 @@ +/* GStreamer + * + * Copyright (c) 2010 Sebastian Dröge <sebastian.droege@collabora.co.uk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <gst/check/gstcheck.h> + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-vp8, " + "width = (int) [1, MAX], " + "height = (int) [1, MAX], " "framerate = (fraction) [0, MAX]")); + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-raw, " + "format = (string) I420, " + "width = (int) [1, MAX], " + "height = (int) [1, MAX], " "framerate = (fraction) [0, MAX]")); + +static GstPad *sinkpad, *srcpad; + +static GstElement * +setup_vp8enc (const gchar * src_caps_str) +{ + GstElement *vp8enc; + GstCaps *srccaps = NULL; + GstBus *bus; + + if (src_caps_str) { + srccaps = gst_caps_from_string (src_caps_str); + fail_unless (srccaps != NULL); + } + + vp8enc = gst_check_setup_element ("vp8enc"); + fail_unless (vp8enc != NULL); + srcpad = gst_check_setup_src_pad (vp8enc, &srctemplate); + sinkpad = gst_check_setup_sink_pad (vp8enc, &sinktemplate); + gst_pad_set_active (srcpad, TRUE); + gst_pad_set_active (sinkpad, TRUE); + gst_check_setup_events (srcpad, vp8enc, srccaps, GST_FORMAT_TIME); + + bus = gst_bus_new (); + gst_element_set_bus (vp8enc, bus); + + fail_unless (gst_element_set_state (vp8enc, + GST_STATE_PLAYING) != GST_STATE_CHANGE_FAILURE, + "could not set to playing"); + + if (srccaps) + gst_caps_unref (srccaps); + + buffers = NULL; + return vp8enc; +} + +static void +cleanup_vp8enc (GstElement * vp8enc) +{ + GstBus *bus; + + /* Free parsed buffers */ + gst_check_drop_buffers (); + + bus = GST_ELEMENT_BUS (vp8enc); + gst_bus_set_flushing (bus, TRUE); + gst_object_unref (bus); + + gst_pad_set_active (srcpad, FALSE); + gst_pad_set_active (sinkpad, FALSE); + gst_check_teardown_src_pad (vp8enc); + gst_check_teardown_sink_pad (vp8enc); + gst_check_teardown_element (vp8enc); +} + +GST_START_TEST (test_encode_simple) +{ + GstElement *vp8enc; + GstBuffer *buffer; + gint i; + GList *l; + GstCaps *outcaps; + GstSegment seg; + + vp8enc = + setup_vp8enc + ("video/x-raw,format=(string)I420,width=(int)320,height=(int)240,framerate=(fraction)25/1"); + + g_object_set (vp8enc, "lag-in-frames", 5, NULL); + + gst_segment_init (&seg, GST_FORMAT_TIME); + seg.stop = gst_util_uint64_scale (20, GST_SECOND, 25); + fail_unless (gst_pad_push_event (srcpad, gst_event_new_segment (&seg))); + + buffer = gst_buffer_new_and_alloc (320 * 240 + 2 * 160 * 120); + gst_buffer_memset (buffer, 0, 0, -1); + + for (i = 0; i < 20; i++) { + GST_BUFFER_TIMESTAMP (buffer) = gst_util_uint64_scale (i, GST_SECOND, 25); + GST_BUFFER_DURATION (buffer) = gst_util_uint64_scale (1, GST_SECOND, 25); + fail_unless (gst_pad_push (srcpad, gst_buffer_ref (buffer)) == GST_FLOW_OK); + } + + gst_buffer_unref (buffer); + + /* Only 5 buffers are allowed to be queued now */ + fail_unless (g_list_length (buffers) > 15); + + fail_unless (gst_pad_push_event (srcpad, gst_event_new_eos ())); + + + /* All buffers must be there now */ + fail_unless_equals_int (g_list_length (buffers), 20); + + outcaps = + gst_caps_from_string + ("video/x-vp8,width=(int)320,height=(int)240,framerate=(fraction)25/1"); + + for (l = buffers, i = 0; l; l = l->next, i++) { + buffer = l->data; + + if (i == 0) + fail_if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT)); + + fail_unless_equals_uint64 (GST_BUFFER_TIMESTAMP (buffer), + gst_util_uint64_scale (i, GST_SECOND, 25)); + fail_unless_equals_uint64 (GST_BUFFER_DURATION (buffer), + gst_util_uint64_scale (1, GST_SECOND, 25)); + } + + gst_caps_unref (outcaps); + + cleanup_vp8enc (vp8enc); +} + +GST_END_TEST; + +static Suite * +vp8enc_suite (void) +{ + Suite *s = suite_create ("vp8enc"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_encode_simple); + + return s; +} + +GST_CHECK_MAIN (vp8enc); diff --git a/tests/check/elements/wavpackdec.c b/tests/check/elements/wavpackdec.c new file mode 100755 index 0000000..9b30db5 --- /dev/null +++ b/tests/check/elements/wavpackdec.c @@ -0,0 +1,258 @@ +/* GStreamer + * + * unit test for wavpackdec + * + * Copyright (c) 2006 Sebastian Dröge <slomo@circular-chaos.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. + */ + +#include <unistd.h> + +#include <gst/check/gstcheck.h> + +/* 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; + +#if G_BYTE_ORDER == G_BIG_ENDIAN +#define AUDIO_FORMAT "S16BE" +#else +#define AUDIO_FORMAT "S16LE" +#endif + +guint8 test_frame[] = { + 0x77, 0x76, 0x70, 0x6B, /* "wvpk" */ + 0x2E, 0x00, 0x00, 0x00, /* ckSize */ + 0x04, 0x04, /* version */ + 0x00, /* track_no */ + 0x00, /* index_no */ + 0x00, 0x64, 0x00, 0x00, /* total_samples */ + 0x00, 0x00, 0x00, 0x00, /* block_index */ + 0x00, 0x64, 0x00, 0x00, /* block_samples */ + 0x05, 0x18, 0x80, 0x04, /* flags */ + 0xFF, 0xAF, 0x80, 0x60, /* crc */ + 0x02, 0x00, 0x03, 0x00, /* data */ + 0x04, 0x00, 0x05, 0x03, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x8A, 0x02, + 0x00, 0x00, 0xFF, 0x7F, + 0x00, 0xE4, +}; + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "format = (string) " AUDIO_FORMAT ", " + "layout = (string) interleaved, " + "channels = (int) 1, " "rate = (int) 44100") + ); + +#define WAVPACK_CAPS "audio/x-wavpack, " \ + "depth = (int) 16, " \ + "channels = (int) 1, " "rate = (int) 44100, " "framed = (boolean) true" + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (WAVPACK_CAPS) + ); + +static GstElement * +setup_wavpackdec (void) +{ + GstElement *wavpackdec; + GstCaps *caps; + + GST_DEBUG ("setup_wavpackdec"); + wavpackdec = gst_check_setup_element ("wavpackdec"); + mysrcpad = gst_check_setup_src_pad (wavpackdec, &srctemplate); + mysinkpad = gst_check_setup_sink_pad (wavpackdec, &sinktemplate); + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + + caps = gst_caps_from_string (WAVPACK_CAPS); + gst_check_setup_events (mysrcpad, wavpackdec, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + + fail_unless (gst_element_set_state (wavpackdec, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + return wavpackdec; +} + +static void +cleanup_wavpackdec (GstElement * wavpackdec) +{ + GST_DEBUG ("cleanup_wavpackdec"); + gst_element_set_state (wavpackdec, GST_STATE_NULL); + + gst_pad_set_active (mysrcpad, FALSE); + gst_pad_set_active (mysinkpad, FALSE); + gst_check_teardown_src_pad (wavpackdec); + gst_check_teardown_sink_pad (wavpackdec); + gst_check_teardown_element (wavpackdec); +} + +GST_START_TEST (test_decode_frame) +{ + GstElement *wavpackdec; + GstBuffer *inbuffer, *outbuffer; + GstBus *bus; + int i; + GstMapInfo map; + + wavpackdec = setup_wavpackdec (); + bus = gst_bus_new (); + + inbuffer = gst_buffer_new_and_alloc (sizeof (test_frame)); + gst_buffer_fill (inbuffer, 0, test_frame, sizeof (test_frame)); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + + gst_element_set_bus (wavpackdec, bus); + + /* should decode the buffer without problems */ + fail_unless_equals_int (gst_pad_push (mysrcpad, inbuffer), GST_FLOW_OK); + + outbuffer = GST_BUFFER (buffers->data); + + fail_if (outbuffer == NULL); + + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + + /* uncompressed data should be 102400 bytes */ + fail_unless_equals_int (map.size, 51200); + + /* and all bytes must be 0, i.e. silence */ + for (i = 0; i < 51200; i++) + fail_unless_equals_int (map.data[i], 0); + + gst_buffer_unmap (outbuffer, &map); + + ASSERT_BUFFER_REFCOUNT (outbuffer, "outbuffer", 1); + gst_buffer_unref (outbuffer); + outbuffer = NULL; + + g_list_free (buffers); + buffers = NULL; + + gst_bus_set_flushing (bus, TRUE); + gst_element_set_bus (wavpackdec, NULL); + gst_object_unref (GST_OBJECT (bus)); + cleanup_wavpackdec (wavpackdec); +} + +GST_END_TEST; + +GST_START_TEST (test_decode_frame_with_broken_header) +{ + GstElement *wavpackdec; + GstBuffer *inbuffer; + GstBus *bus; + GstMessage *message; + + wavpackdec = setup_wavpackdec (); + bus = gst_bus_new (); + + inbuffer = gst_buffer_new_and_alloc (sizeof (test_frame)); + gst_buffer_fill (inbuffer, 0, test_frame, sizeof (test_frame)); + /* break header */ + gst_buffer_memset (inbuffer, 2, 'e', 1); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + + gst_element_set_bus (wavpackdec, bus); + + /* should fail gracefully */ + fail_unless_equals_int (gst_pad_push (mysrcpad, inbuffer), GST_FLOW_ERROR); + + fail_if ((message = gst_bus_pop (bus)) == NULL); + fail_unless_message_error (message, STREAM, DECODE); + gst_message_unref (message); + + gst_element_set_bus (wavpackdec, NULL); + gst_object_unref (GST_OBJECT (bus)); + cleanup_wavpackdec (wavpackdec); +} + +GST_END_TEST; + +GST_START_TEST (test_decode_frame_with_incomplete_frame) +{ + GstElement *wavpackdec; + GstBuffer *inbuffer; + GstBus *bus; + GstMessage *message; + + wavpackdec = setup_wavpackdec (); + bus = gst_bus_new (); + + inbuffer = gst_buffer_new_and_alloc (sizeof (test_frame) - 2); + gst_buffer_fill (inbuffer, 0, test_frame, sizeof (test_frame) - 2); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + + gst_element_set_bus (wavpackdec, bus); + + /* should fail gracefully */ + fail_unless_equals_int (gst_pad_push (mysrcpad, inbuffer), GST_FLOW_ERROR); + + fail_if ((message = gst_bus_pop (bus)) == NULL); + fail_unless_message_error (message, STREAM, DECODE); + gst_message_unref (message); + + + gst_element_set_bus (wavpackdec, NULL); + gst_object_unref (GST_OBJECT (bus)); + cleanup_wavpackdec (wavpackdec); +} + +GST_END_TEST; + +static Suite * +wavpackdec_suite (void) +{ + Suite *s = suite_create ("wavpackdec"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_decode_frame); + tcase_add_test (tc_chain, test_decode_frame_with_broken_header); + tcase_add_test (tc_chain, test_decode_frame_with_incomplete_frame); + + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = wavpackdec_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/wavpackenc.c b/tests/check/elements/wavpackenc.c new file mode 100644 index 0000000..22e2e7c --- /dev/null +++ b/tests/check/elements/wavpackenc.c @@ -0,0 +1,197 @@ +/* GStreamer + * + * unit test for wavpackenc + * + * Copyright (c) 2006 Sebastian Dröge <slomo@circular-chaos.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. + */ + +#include <unistd.h> + +#include <gst/check/gstcheck.h> + +/* 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; +static GstBus *bus; + +#if G_BYTE_ORDER == G_BIG_ENDIAN +#define AUDIO_FORMAT "S32BE" +#else +#define AUDIO_FORMAT "S32LE" +#endif + +#define RAW_CAPS_STRING "audio/x-raw, " \ + "format = (string) " AUDIO_FORMAT ", " \ + "layout = (string) interleaved, " \ + "channels = (int) 1, " \ + "rate = (int) 44100" + +#define WAVPACK_CAPS_STRING "audio/x-wavpack, " \ + "depth = (int) 32, " \ + "channels = (int) 1, " \ + "rate = (int) 44100, " \ + "framed = (boolean) true" + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-wavpack, " + "depth = (int) 32, " + "channels = (int) 1, " + "rate = (int) 44100, " "framed = (boolean) true")); + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "format = (string) " AUDIO_FORMAT ", " + "layout = (string) interleaved, " + "channels = (int) 1, " "rate = (int) 44100")); + +static GstElement * +setup_wavpackenc (void) +{ + GstElement *wavpackenc; + + GST_DEBUG ("setup_wavpackenc"); + wavpackenc = gst_check_setup_element ("wavpackenc"); + mysrcpad = gst_check_setup_src_pad (wavpackenc, &srctemplate); + mysinkpad = gst_check_setup_sink_pad (wavpackenc, &sinktemplate); + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + + fail_unless (gst_element_set_state (wavpackenc, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + bus = gst_bus_new (); + + gst_pad_push_event (mysrcpad, gst_event_new_stream_start ("test-silence")); + + return wavpackenc; +} + +static void +cleanup_wavpackenc (GstElement * wavpackenc) +{ + GST_DEBUG ("cleanup_wavpackenc"); + + gst_bus_set_flushing (bus, TRUE); + gst_element_set_bus (wavpackenc, NULL); + gst_object_unref (GST_OBJECT (bus)); + + gst_element_set_state (wavpackenc, GST_STATE_NULL); + + gst_pad_set_active (mysrcpad, FALSE); + gst_pad_set_active (mysinkpad, FALSE); + gst_check_teardown_src_pad (wavpackenc); + gst_check_teardown_sink_pad (wavpackenc); + gst_check_teardown_element (wavpackenc); +} + +GST_START_TEST (test_encode_silence) +{ + GstSegment segment; + GstElement *wavpackenc; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + GstEvent *eos = gst_event_new_eos (); + int i, num_buffers; + + wavpackenc = setup_wavpackenc (); + + gst_segment_init (&segment, GST_FORMAT_TIME); + + inbuffer = gst_buffer_new_and_alloc (1000); + gst_buffer_memset (inbuffer, 0, 0, 1000); + + caps = gst_caps_from_string (RAW_CAPS_STRING); + fail_unless (gst_pad_set_caps (mysrcpad, caps)); + gst_caps_unref (caps); + + gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment)); + + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + gst_element_set_bus (wavpackenc, bus); + + fail_unless_equals_int (gst_pad_push (mysrcpad, inbuffer), GST_FLOW_OK); + + fail_if (gst_pad_push_event (mysrcpad, eos) != TRUE); + + /* check first buffer */ + outbuffer = GST_BUFFER (buffers->data); + + fail_if (outbuffer == NULL); + + fail_unless_equals_int (GST_BUFFER_TIMESTAMP (outbuffer), 0); + fail_unless_equals_int (GST_BUFFER_DURATION (outbuffer), 5668934); + + fail_unless (gst_buffer_memcmp (outbuffer, 0, "wvpk", 4) == 0, + "Failed to encode to valid Wavpack frames"); + + /* free all buffers */ + num_buffers = g_list_length (buffers); + + for (i = 0; i < num_buffers; ++i) { + outbuffer = GST_BUFFER (buffers->data); + fail_if (outbuffer == NULL); + + buffers = g_list_remove (buffers, outbuffer); + + gst_buffer_unref (outbuffer); + outbuffer = NULL; + } + + g_list_free (buffers); + buffers = NULL; + + cleanup_wavpackenc (wavpackenc); +} + +GST_END_TEST; + +static Suite * +wavpackenc_suite (void) +{ + Suite *s = suite_create ("wavpackenc"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_encode_silence); + + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = wavpackenc_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/wavpackparse.c b/tests/check/elements/wavpackparse.c new file mode 100644 index 0000000..ec40909 --- /dev/null +++ b/tests/check/elements/wavpackparse.c @@ -0,0 +1,243 @@ +/* GStreamer + * + * unit test for wavpackparse + * + * Copyright (c) 2006 Sebastian Dröge <slomo@circular-chaos.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. + */ + +#include <unistd.h> + +#include <gst/check/gstcheck.h> + +/* 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; + +/* Wavpack file with 2 frames of silence */ +guint8 test_file[] = { + 0x77, 0x76, 0x70, 0x6B, 0x62, 0x00, 0x00, 0x00, /* first frame */ + 0x04, 0x04, 0x00, 0x00, 0x00, 0xC8, 0x00, 0x00, /* include RIFF header */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, + 0x05, 0x18, 0x80, 0x04, 0xFF, 0xAF, 0x80, 0x60, + 0x21, 0x16, 0x52, 0x49, 0x46, 0x46, 0x24, 0x90, + 0x01, 0x00, 0x57, 0x41, 0x56, 0x45, 0x66, 0x6D, + 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x44, 0xAC, 0x00, 0x00, 0x88, 0x58, + 0x01, 0x00, 0x02, 0x00, 0x10, 0x00, 0x64, 0x61, + 0x74, 0x61, 0x00, 0x90, 0x01, 0x00, 0x02, 0x00, + 0x03, 0x00, 0x04, 0x00, 0x05, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x65, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x8A, 0x02, 0x00, 0x00, 0xFF, 0x7F, + 0x00, 0xE4, + 0x77, 0x76, 0x70, 0x6B, 0x2E, 0x00, 0x00, 0x00, /* second frame */ + 0x04, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x64, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, + 0x05, 0x18, 0x80, 0x04, 0xFF, 0xAF, 0x80, 0x60, + 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8A, 0x02, + 0x00, 0x00, 0xFF, 0x7F, 0x00, 0xE4, +}; + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-wavpack, " + "depth = (int) 16, " + "channels = (int) 1, " + "rate = (int) 44100, " "framed = (boolean) TRUE")); + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-wavpack")); + +static GstElement * +setup_wavpackparse (void) +{ + GstElement *wavpackparse; + + GST_DEBUG ("setup_wavpackparse"); + + wavpackparse = gst_check_setup_element ("wavpackparse"); + mysrcpad = gst_check_setup_src_pad (wavpackparse, &srctemplate); + mysinkpad = gst_check_setup_sink_pad (wavpackparse, &sinktemplate); + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + gst_check_setup_events (mysrcpad, wavpackparse, NULL, GST_FORMAT_BYTES); + + return wavpackparse; +} + +static void +cleanup_wavpackparse (GstElement * wavpackparse) +{ + GST_DEBUG ("cleanup_wavpackparse"); + gst_element_set_state (wavpackparse, GST_STATE_NULL); + + gst_pad_set_active (mysrcpad, FALSE); + gst_pad_set_active (mysinkpad, FALSE); + gst_check_teardown_src_pad (wavpackparse); + gst_check_teardown_sink_pad (wavpackparse); + gst_check_teardown_element (wavpackparse); +} + +GST_START_TEST (test_parsing_valid_frames) +{ + GstElement *wavpackparse; + GstBuffer *inbuffer, *outbuffer; + int i, num_buffers; + GstFormat format = GST_FORMAT_TIME; + gint64 pos; + + wavpackparse = setup_wavpackparse (); + fail_unless (gst_element_set_state (wavpackparse, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (sizeof (test_file)); + gst_buffer_fill (inbuffer, 0, test_file, sizeof (test_file)); + + /* should decode the buffer without problems */ + fail_unless_equals_int (gst_pad_push (mysrcpad, inbuffer), GST_FLOW_OK); + + num_buffers = g_list_length (buffers); + /* should get 2 buffers, each one complete wavpack frame */ + fail_unless_equals_int (num_buffers, 2); + + for (i = 0; i < num_buffers; ++i) { + outbuffer = GST_BUFFER (buffers->data); + fail_if (outbuffer == NULL); + + fail_unless (gst_buffer_memcmp (outbuffer, 0, "wvpk", 4) == 0, + "Buffer contains no Wavpack frame"); + fail_unless_equals_int (GST_BUFFER_DURATION (outbuffer), 580498866); + + switch (i) { + case 0:{ + fail_unless_equals_int (GST_BUFFER_TIMESTAMP (outbuffer), 0); + break; + } + case 1:{ + fail_unless_equals_int (GST_BUFFER_TIMESTAMP (outbuffer), 580498866); + break; + } + } + + buffers = g_list_remove (buffers, outbuffer); + + gst_buffer_unref (outbuffer); + outbuffer = NULL; + } + + fail_unless (gst_element_query_position (wavpackparse, format, &pos), + "Position query failed"); + fail_unless_equals_int64 (pos, 580498866 * 2); + fail_unless (gst_element_query_duration (wavpackparse, format, NULL), + "Duration query failed"); + + g_list_free (buffers); + buffers = NULL; + + cleanup_wavpackparse (wavpackparse); +} + +GST_END_TEST; + +GST_START_TEST (test_parsing_invalid_first_header) +{ + GstElement *wavpackparse; + GstBuffer *inbuffer, *outbuffer; + int i, num_buffers; + + wavpackparse = setup_wavpackparse (); + fail_unless (gst_element_set_state (wavpackparse, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (sizeof (test_file)); + gst_buffer_fill (inbuffer, 0, test_file, sizeof (test_file)); + gst_buffer_memset (inbuffer, 0, 'k', 1); + + /* should decode the buffer without problems */ + fail_unless_equals_int (gst_pad_push (mysrcpad, inbuffer), GST_FLOW_OK); + + num_buffers = g_list_length (buffers); + + /* should get 1 buffers, the second non-broken one */ + fail_unless_equals_int (num_buffers, 1); + + for (i = 0; i < num_buffers; ++i) { + outbuffer = GST_BUFFER (buffers->data); + fail_if (outbuffer == NULL); + + fail_unless (gst_buffer_memcmp (outbuffer, 0, "wvpk", 4) == 0, + "Buffer contains no Wavpack frame"); + fail_unless_equals_int (GST_BUFFER_DURATION (outbuffer), 580498866); + + switch (i) { + case 0:{ + fail_unless_equals_int (GST_BUFFER_TIMESTAMP (outbuffer), 580498866); + break; + } + } + + buffers = g_list_remove (buffers, outbuffer); + + gst_buffer_unref (outbuffer); + outbuffer = NULL; + } + + g_list_free (buffers); + buffers = NULL; + + cleanup_wavpackparse (wavpackparse); +} + +GST_END_TEST; + + +static Suite * +wavpackparse_suite (void) +{ + Suite *s = suite_create ("wavpackparse"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_parsing_valid_frames); + tcase_add_test (tc_chain, test_parsing_invalid_first_header); + + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = wavpackparse_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/check/elements/wavparse.c b/tests/check/elements/wavparse.c new file mode 100755 index 0000000..10e2ea7 --- /dev/null +++ b/tests/check/elements/wavparse.c @@ -0,0 +1,87 @@ +/* GStreamer WavParse unit tests + * + * 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 + +#include <gst/check/gstcheck.h> + +static void +do_test_empty_file (gboolean can_activate_pull) +{ + GstStateChangeReturn ret1, ret2; + GstElement *pipeline; + GstElement *src; + GstElement *wavparse; + GstElement *fakesink; + + /* Pull mode */ + pipeline = gst_pipeline_new ("testpipe"); + src = gst_element_factory_make ("fakesrc", NULL); + fail_if (src == NULL); + wavparse = gst_element_factory_make ("wavparse", NULL); + fail_if (wavparse == NULL); + fakesink = gst_element_factory_make ("fakesink", NULL); + fail_if (fakesink == NULL); + + gst_bin_add_many (GST_BIN (pipeline), src, wavparse, fakesink, NULL); + g_object_set (src, "num-buffers", 0, "can-activate-pull", can_activate_pull, + NULL); + + fail_unless (gst_element_link_many (src, wavparse, fakesink, NULL)); + + ret1 = gst_element_set_state (pipeline, GST_STATE_PLAYING); + if (ret1 == GST_STATE_CHANGE_ASYNC) + ret2 = gst_element_get_state (pipeline, NULL, NULL, GST_CLOCK_TIME_NONE); + else + ret2 = ret1; + + /* should have gotten an error on the bus, no output to fakesink */ + fail_unless_equals_int (ret2, GST_STATE_CHANGE_FAILURE); + + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_object_unref (pipeline); +} + +GST_START_TEST (test_empty_file_pull) +{ + do_test_empty_file (TRUE); +} + +GST_END_TEST; + +GST_START_TEST (test_empty_file_push) +{ + do_test_empty_file (FALSE); +} + +GST_END_TEST; + +static Suite * +wavparse_suite (void) +{ + Suite *s = suite_create ("wavparse"); + TCase *tc_chain = tcase_create ("wavparse"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_empty_file_pull); + tcase_add_test (tc_chain, test_empty_file_push); + return s; +} + +GST_CHECK_MAIN (wavparse) diff --git a/tests/check/elements/y4menc.c b/tests/check/elements/y4menc.c new file mode 100644 index 0000000..2117ada --- /dev/null +++ b/tests/check/elements/y4menc.c @@ -0,0 +1,183 @@ +/* GStreamer + * + * unit test for y4menc + * + * Copyright (C) <2006> Mark Nauwelaerts <manauw@skynet.be> + * + * 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 <unistd.h> + +#include <gst/check/gstcheck.h> + +/* 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; + +#define VIDEO_CAPS_STRING "video/x-raw, " \ + "format = (string) I420, "\ + "width = (int) 384, " \ + "height = (int) 288, " \ + "framerate = (fraction) 25/1, " \ + "pixel-aspect-ratio = (fraction) 1/1" + +#define Y4M_CAPS_STRING "application/x-yuv4mpeg, " \ + "y4mversion = (int) 2" + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (Y4M_CAPS_STRING)); + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (VIDEO_CAPS_STRING)); + + +static GstElement * +setup_y4menc (void) +{ + GstElement *y4menc; + + GST_DEBUG ("setup_y4menc"); + y4menc = gst_check_setup_element ("y4menc"); + mysrcpad = gst_check_setup_src_pad (y4menc, &srctemplate); + mysinkpad = gst_check_setup_sink_pad (y4menc, &sinktemplate); + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + + return y4menc; +} + +static void +cleanup_y4menc (GstElement * y4menc) +{ + GST_DEBUG ("cleanup_y4menc"); + gst_element_set_state (y4menc, GST_STATE_NULL); + + gst_pad_set_active (mysrcpad, FALSE); + gst_pad_set_active (mysinkpad, FALSE); + gst_check_teardown_src_pad (y4menc); + gst_check_teardown_sink_pad (y4menc); + gst_check_teardown_element (y4menc); +} + +GST_START_TEST (test_y4m) +{ + GstElement *y4menc; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + int i, num_buffers, size; + const gchar *data0 = "YUV4MPEG2 W384 H288 Ip F25:1 A1:1\n"; + const gchar *data1 = "YUV4MPEG2 C420 W384 H288 Ip F25:1 A1:1\n"; + const gchar *data2 = "FRAME\n"; + + y4menc = setup_y4menc (); + fail_unless (gst_element_set_state (y4menc, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + /* corresponds to I420 buffer for the size mentioned in the caps */ + size = 384 * 288 * 3 / 2; + inbuffer = gst_buffer_new_and_alloc (size); + /* makes valgrind's memcheck happier */ + gst_buffer_memset (inbuffer, 0, 0, size); + caps = gst_caps_from_string (VIDEO_CAPS_STRING); + gst_check_setup_events (mysrcpad, y4menc, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + + num_buffers = g_list_length (buffers); + fail_unless (num_buffers == 1); + + /* clean up buffers */ + for (i = 0; i < num_buffers; ++i) { + GstMapInfo map; + gchar *data; + gsize outsize; + + outbuffer = GST_BUFFER (buffers->data); + fail_if (outbuffer == NULL); + + switch (i) { + case 0: + gst_buffer_map (outbuffer, &map, GST_MAP_READ); + outsize = map.size; + data = (gchar *) map.data; + + fail_unless (outsize > size); + fail_unless (memcmp (data, data0, strlen (data0)) == 0 || + memcmp (data, data1, strlen (data1)) == 0); + /* so we know there is a newline */ + data = strchr (data, '\n'); + fail_unless (data != NULL); + data++; + fail_unless (memcmp (data2, data, strlen (data2)) == 0); + data += strlen (data2); + /* remainder must be frame data */ + fail_unless (data - (gchar *) map.data + size == outsize); + gst_buffer_unmap (outbuffer, &map); + break; + default: + break; + } + buffers = g_list_remove (buffers, outbuffer); + + ASSERT_BUFFER_REFCOUNT (outbuffer, "outbuffer", 1); + gst_buffer_unref (outbuffer); + outbuffer = NULL; + } + + cleanup_y4menc (y4menc); + g_list_free (buffers); + buffers = NULL; +} + +GST_END_TEST; + +static Suite * +y4menc_suite (void) +{ + Suite *s = suite_create ("y4menc"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_y4m); + + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = y4menc_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} |