1 /* GStreamer
2  *
3  * unit test for rawaudioparse
4  *
5  * Copyright (C) <2016> Carlos Rafael Giani <dv at pseudoterminal dot org>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 
26 /* FIXME: GValueArray is deprecated, but there is currently no viable alternatives
27  * See https://bugzilla.gnome.org/show_bug.cgi?id=667228 */
28 #define GLIB_DISABLE_DEPRECATION_WARNINGS
29 
30 #include <gst/check/gstcheck.h>
31 #include <gst/audio/audio.h>
32 
33 /* Checks are hardcoded to expect stereo 16-bit data. The sample rate
34  * however varies from the default of 40 kHz in some tests to see the
35  * differences in calculated buffer durations. */
36 #define NUM_TEST_SAMPLES 512
37 #define NUM_TEST_CHANNELS 2
38 #define TEST_SAMPLE_RATE 40000
39 #define TEST_SAMPLE_FORMAT GST_AUDIO_FORMAT_S16
40 
41 /* For ease of programming we use globals to keep refs for our floating
42  * src and sink pads we create; otherwise we always have to do get_pad,
43  * get_peer, and then remove references in every test function */
44 static GstPad *mysrcpad, *mysinkpad;
45 
46 typedef struct
47 {
48   GstElement *rawaudioparse;
49   GstAdapter *test_data_adapter;
50 }
51 RawAudParseTestCtx;
52 
53 /* Sets up a rawaudioparse element and a GstAdapter that contains 512 test
54  * audio samples. The samples a monotonically increasing set from the values
55  * 0 to 511 for the left and 512 to 1023 for the right channel. The result
56  * is a GstAdapter that contains the interleaved 16-bit integer values:
57  * 0,512,1,513,2,514, ... 511,1023 . This set is used in the checks to see
58  * if rawaudioparse's output buffers contain valid data. */
59 static void
setup_rawaudioparse(RawAudParseTestCtx * testctx,gboolean use_sink_caps,gboolean set_properties,GstCaps * incaps,GstFormat format)60 setup_rawaudioparse (RawAudParseTestCtx * testctx, gboolean use_sink_caps,
61     gboolean set_properties, GstCaps * incaps, GstFormat format)
62 {
63   GstElement *rawaudioparse;
64   GstAdapter *test_data_adapter;
65   GstBuffer *buffer;
66   guint i;
67   guint16 samples[NUM_TEST_SAMPLES * NUM_TEST_CHANNELS];
68 
69 
70   /* Setup the rawaudioparse element and the pads */
71 
72   static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
73       GST_PAD_SINK,
74       GST_PAD_ALWAYS,
75       GST_STATIC_CAPS (GST_AUDIO_CAPS_MAKE (GST_AUDIO_FORMATS_ALL))
76       );
77   static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
78       GST_PAD_SRC,
79       GST_PAD_ALWAYS,
80       GST_STATIC_CAPS_ANY);
81 
82   rawaudioparse = gst_check_setup_element ("rawaudioparse");
83 
84   g_object_set (G_OBJECT (rawaudioparse), "use-sink-caps", use_sink_caps, NULL);
85   if (set_properties)
86     g_object_set (G_OBJECT (rawaudioparse), "sample-rate", TEST_SAMPLE_RATE,
87         "num-channels", NUM_TEST_CHANNELS, "pcm-format", TEST_SAMPLE_FORMAT,
88         NULL);
89 
90   fail_unless (gst_element_set_state (rawaudioparse,
91           GST_STATE_PAUSED) == GST_STATE_CHANGE_SUCCESS,
92       "could not set to paused");
93 
94   mysrcpad = gst_check_setup_src_pad (rawaudioparse, &srctemplate);
95   mysinkpad = gst_check_setup_sink_pad (rawaudioparse, &sinktemplate);
96 
97   gst_pad_set_active (mysrcpad, TRUE);
98   gst_pad_set_active (mysinkpad, TRUE);
99 
100   gst_check_setup_events (mysrcpad, rawaudioparse, incaps, format);
101   if (incaps)
102     gst_caps_unref (incaps);
103 
104 
105   /* Fill the adapter with the interleaved 0..511 and
106    * 512..1023 samples */
107   for (i = 0; i < NUM_TEST_SAMPLES; ++i) {
108     guint c;
109     for (c = 0; c < NUM_TEST_CHANNELS; ++c)
110       samples[i * NUM_TEST_CHANNELS + c] = c * NUM_TEST_SAMPLES + i;
111   }
112 
113   test_data_adapter = gst_adapter_new ();
114   buffer = gst_buffer_new_allocate (NULL, sizeof (samples), NULL);
115   gst_buffer_fill (buffer, 0, samples, sizeof (samples));
116   gst_adapter_push (test_data_adapter, buffer);
117 
118 
119   testctx->rawaudioparse = rawaudioparse;
120   testctx->test_data_adapter = test_data_adapter;
121 }
122 
123 static void
cleanup_rawaudioparse(RawAudParseTestCtx * testctx)124 cleanup_rawaudioparse (RawAudParseTestCtx * testctx)
125 {
126   int num_buffers, i;
127 
128   gst_pad_set_active (mysrcpad, FALSE);
129   gst_pad_set_active (mysinkpad, FALSE);
130   gst_check_teardown_src_pad (testctx->rawaudioparse);
131   gst_check_teardown_sink_pad (testctx->rawaudioparse);
132   gst_check_teardown_element (testctx->rawaudioparse);
133 
134   g_object_unref (G_OBJECT (testctx->test_data_adapter));
135 
136   if (buffers != NULL) {
137     num_buffers = g_list_length (buffers);
138     for (i = 0; i < num_buffers; ++i) {
139       GstBuffer *buf = GST_BUFFER (buffers->data);
140       buffers = g_list_remove (buffers, buf);
141       gst_buffer_unref (buf);
142     }
143 
144     g_list_free (buffers);
145     buffers = NULL;
146   }
147 }
148 
149 
150 static void
push_data_and_check_output(RawAudParseTestCtx * testctx,gsize num_in_bytes,gsize expected_num_out_bytes,gint64 expected_pts,gint64 expected_dur,guint expected_num_buffers_in_list,guint bpf,guint16 channel0_start,guint16 channel1_start)151 push_data_and_check_output (RawAudParseTestCtx * testctx, gsize num_in_bytes,
152     gsize expected_num_out_bytes, gint64 expected_pts, gint64 expected_dur,
153     guint expected_num_buffers_in_list, guint bpf, guint16 channel0_start,
154     guint16 channel1_start)
155 {
156   GstBuffer *inbuf, *outbuf;
157   guint num_buffers;
158 
159   /* Simulate upstream input by taking num_in_bytes bytes from the adapter */
160   inbuf = gst_adapter_take_buffer (testctx->test_data_adapter, num_in_bytes);
161   fail_unless (inbuf != NULL);
162 
163   /* Push the input data and check that the output buffers list grew as
164    * expected */
165   fail_unless (gst_pad_push (mysrcpad, inbuf) == GST_FLOW_OK);
166   num_buffers = g_list_length (buffers);
167   fail_unless_equals_int (num_buffers, expected_num_buffers_in_list);
168 
169   /* Take the latest output buffer */
170   outbuf = g_list_nth_data (buffers, num_buffers - 1);
171   fail_unless (outbuf != NULL);
172 
173   /* Verify size, PTS, duration of the output buffer */
174   fail_unless_equals_uint64 (expected_num_out_bytes,
175       gst_buffer_get_size (outbuf));
176   fail_unless_equals_uint64 (expected_pts, GST_BUFFER_PTS (outbuf));
177   fail_unless_equals_uint64 (expected_dur, GST_BUFFER_DURATION (outbuf));
178 
179   /* Go through all of the samples in the output buffer and check that they are
180    * valid. The samples are interleaved. The offsets specified by channel0_start
181    * and channel1_start are the expected values of the first sample for each
182    * channel in the buffer. So, if channel0_start is 512, then sample #0 in the
183    * buffer must have value 512, and if channel1_start is 700, then sample #1
184    * in the buffer must have value 700 etc. */
185   {
186     guint i, num_frames;
187     guint16 *s;
188     GstMapInfo map_info;
189     guint channel_starts[2] = { channel0_start, channel1_start };
190 
191     gst_buffer_map (outbuf, &map_info, GST_MAP_READ);
192     num_frames = map_info.size / bpf;
193     s = (guint16 *) (map_info.data);
194 
195     for (i = 0; i < num_frames; ++i) {
196       guint c;
197 
198       for (c = 0; i < NUM_TEST_CHANNELS; ++i) {
199         guint16 expected = channel_starts[c] + i;
200         guint16 actual = s[i * NUM_TEST_CHANNELS + c];
201 
202         fail_unless_equals_int (expected, actual);
203       }
204     }
205 
206     gst_buffer_unmap (outbuf, &map_info);
207   }
208 }
209 
210 
GST_START_TEST(test_push_unaligned_data_properties_config)211 GST_START_TEST (test_push_unaligned_data_properties_config)
212 {
213   RawAudParseTestCtx testctx;
214 
215   setup_rawaudioparse (&testctx, FALSE, TRUE, NULL, GST_FORMAT_BYTES);
216 
217   /* Send in data buffers that are not aligned to multiples of the
218    * frame size (= sample size * num_channels). This tests if rawaudioparse
219    * aligns output data properly.
220    *
221    * The second line sends in 99 bytes, and expects 100 bytes in the
222    * output buffer. This is because the first buffer contains 45 bytes,
223    * and rawaudioparse is expected to output 44 bytes (which is an integer
224    * multiple of the frame size). The leftover 1 byte then gets prepended
225    * to the input buffer with 99 bytes, resulting in 100 bytes, which is
226    * an integer multiple of the frame size.
227    */
228 
229   push_data_and_check_output (&testctx, 45, 44, GST_USECOND * 0,
230       GST_USECOND * 275, 1, 4, 0, 512);
231   push_data_and_check_output (&testctx, 99, 100, GST_USECOND * 275,
232       GST_USECOND * 625, 2, 4, 11, 523);
233   push_data_and_check_output (&testctx, 18, 16, GST_USECOND * 900,
234       GST_USECOND * 100, 3, 4, 36, 548);
235 
236   cleanup_rawaudioparse (&testctx);
237 }
238 
239 GST_END_TEST;
240 
GST_START_TEST(test_push_unaligned_data_sink_caps_config)241 GST_START_TEST (test_push_unaligned_data_sink_caps_config)
242 {
243   RawAudParseTestCtx testctx;
244   GstAudioInfo ainfo;
245   GstCaps *caps;
246 
247   /* This test is essentially the same as test_push_unaligned_data_properties_config,
248    * except that rawaudioparse uses the sink caps config instead of the property config. */
249 
250   gst_audio_info_set_format (&ainfo, TEST_SAMPLE_FORMAT, TEST_SAMPLE_RATE,
251       NUM_TEST_CHANNELS, NULL);
252   caps = gst_audio_info_to_caps (&ainfo);
253 
254   setup_rawaudioparse (&testctx, TRUE, FALSE, caps, GST_FORMAT_BYTES);
255 
256   push_data_and_check_output (&testctx, 45, 44, GST_USECOND * 0,
257       GST_USECOND * 275, 1, 4, 0, 512);
258   push_data_and_check_output (&testctx, 99, 100, GST_USECOND * 275,
259       GST_USECOND * 625, 2, 4, 11, 523);
260   push_data_and_check_output (&testctx, 18, 16, GST_USECOND * 900,
261       GST_USECOND * 100, 3, 4, 36, 548);
262 
263   cleanup_rawaudioparse (&testctx);
264 }
265 
266 GST_END_TEST;
267 
GST_START_TEST(test_push_swapped_channels)268 GST_START_TEST (test_push_swapped_channels)
269 {
270   RawAudParseTestCtx testctx;
271   GValueArray *valarray;
272   GValue val = G_VALUE_INIT;
273 
274   /* Send in 40 bytes and use a nonstandard channel order (left and right channels
275    * swapped). Expected behavior is for rawaudioparse to reorder the samples inside
276    * output buffers to conform to the GStreamer channel order. For this reason,
277    * channel0 offset is 512 and channel1 offset is 0 in the check below. */
278 
279   setup_rawaudioparse (&testctx, FALSE, TRUE, NULL, GST_FORMAT_BYTES);
280 
281   valarray = g_value_array_new (2);
282   g_value_init (&val, GST_TYPE_AUDIO_CHANNEL_POSITION);
283   g_value_set_enum (&val, GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT);
284   g_value_array_insert (valarray, 0, &val);
285   g_value_set_enum (&val, GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT);
286   g_value_array_insert (valarray, 1, &val);
287   g_object_set (G_OBJECT (testctx.rawaudioparse), "channel-positions",
288       valarray, NULL);
289   g_value_array_free (valarray);
290   g_value_unset (&val);
291 
292   push_data_and_check_output (&testctx, 40, 40, GST_USECOND * 0,
293       GST_USECOND * 250, 1, 4, 512, 0);
294 
295   cleanup_rawaudioparse (&testctx);
296 }
297 
298 GST_END_TEST;
299 
GST_START_TEST(test_config_switch)300 GST_START_TEST (test_config_switch)
301 {
302   RawAudParseTestCtx testctx;
303   GstAudioInfo ainfo;
304   GstCaps *caps;
305 
306   /* Start processing with the properties config active, then mid-stream switch to
307    * the sink caps config. The properties config is altered to have a different
308    * sample rate than the sink caps to be able to detect the switch. The net effect
309    * is that output buffer durations are altered. For example, 40 bytes equal
310    * 10 samples, and this equals 500 us with 20 kHz or 250 us with 40 kHz. */
311 
312   gst_audio_info_set_format (&ainfo, TEST_SAMPLE_FORMAT, TEST_SAMPLE_RATE,
313       NUM_TEST_CHANNELS, NULL);
314   caps = gst_audio_info_to_caps (&ainfo);
315 
316   setup_rawaudioparse (&testctx, FALSE, TRUE, caps, GST_FORMAT_BYTES);
317 
318   g_object_set (G_OBJECT (testctx.rawaudioparse), "sample-rate", 20000, NULL);
319 
320   /* Push in data with properties config active, expecting duration calculations
321    * to be based on the 20 kHz sample rate */
322   push_data_and_check_output (&testctx, 40, 40, GST_USECOND * 0,
323       GST_USECOND * 500, 1, 4, 0, 512);
324   push_data_and_check_output (&testctx, 20, 20, GST_USECOND * 500,
325       GST_USECOND * 250, 2, 4, 10, 522);
326 
327   /* Perform the switch */
328   g_object_set (G_OBJECT (testctx.rawaudioparse), "use-sink-caps", TRUE, NULL);
329 
330   /* Push in data with sink caps config active, expecting duration calculations
331    * to be based on the 40 kHz sample rate */
332   push_data_and_check_output (&testctx, 40, 40, GST_USECOND * 750,
333       GST_USECOND * 250, 3, 4, 15, 527);
334 
335   cleanup_rawaudioparse (&testctx);
336 }
337 
338 GST_END_TEST;
339 
GST_START_TEST(test_change_caps)340 GST_START_TEST (test_change_caps)
341 {
342   RawAudParseTestCtx testctx;
343   GstAudioInfo ainfo;
344   GstCaps *caps;
345 
346   /* Start processing with the sink caps config active, using the
347    * default channel count and sample format and 20 kHz sample rate
348    * for the caps. Push some data, then change caps (20 kHz -> 40 kHz).
349    * Check that the changed caps are handled properly. */
350 
351   gst_audio_info_set_format (&ainfo, TEST_SAMPLE_FORMAT, 20000,
352       NUM_TEST_CHANNELS, NULL);
353   caps = gst_audio_info_to_caps (&ainfo);
354 
355   setup_rawaudioparse (&testctx, TRUE, FALSE, caps, GST_FORMAT_BYTES);
356 
357   /* Push in data with caps sink config active, expecting duration calculations
358    * to be based on the 20 kHz sample rate */
359   push_data_and_check_output (&testctx, 40, 40, GST_USECOND * 0,
360       GST_USECOND * 500, 1, 4, 0, 512);
361   push_data_and_check_output (&testctx, 20, 20, GST_USECOND * 500,
362       GST_USECOND * 250, 2, 4, 10, 522);
363 
364   /* Change caps */
365   gst_audio_info_set_format (&ainfo, TEST_SAMPLE_FORMAT, 40000,
366       NUM_TEST_CHANNELS, NULL);
367   caps = gst_audio_info_to_caps (&ainfo);
368   fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_caps (caps)));
369   gst_caps_unref (caps);
370 
371   /* Push in data with the new caps, expecting duration calculations
372    * to be based on the 40 kHz sample rate */
373   push_data_and_check_output (&testctx, 40, 40, GST_USECOND * 750,
374       GST_USECOND * 250, 3, 4, 15, 527);
375 
376   cleanup_rawaudioparse (&testctx);
377 }
378 
379 GST_END_TEST;
380 
381 
382 static Suite *
rawaudioparse_suite(void)383 rawaudioparse_suite (void)
384 {
385   Suite *s = suite_create ("rawaudioparse");
386   TCase *tc_chain = tcase_create ("general");
387 
388   suite_add_tcase (s, tc_chain);
389   tcase_add_test (tc_chain, test_push_unaligned_data_properties_config);
390   tcase_add_test (tc_chain, test_push_unaligned_data_sink_caps_config);
391   tcase_add_test (tc_chain, test_push_swapped_channels);
392   tcase_add_test (tc_chain, test_config_switch);
393   tcase_add_test (tc_chain, test_change_caps);
394 
395   return s;
396 }
397 
398 GST_CHECK_MAIN (rawaudioparse);
399