summaryrefslogtreecommitdiffstats
path: root/src/tests
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/tests/alsa-mixer-path-test.c120
-rw-r--r--src/tests/alsa-time-test.c251
-rw-r--r--src/tests/asyncmsgq-test.c125
-rw-r--r--src/tests/asyncq-test.c107
-rw-r--r--src/tests/atomic-test.c135
-rw-r--r--src/tests/channelmap-test.c58
-rw-r--r--src/tests/close-test.c20
-rw-r--r--src/tests/connect-stress.c235
-rw-r--r--src/tests/core-util-test.c284
-rw-r--r--src/tests/cpu-mix-test.c224
-rw-r--r--src/tests/cpu-remap-test.c540
-rw-r--r--src/tests/cpu-sconv-test.c263
-rw-r--r--src/tests/cpu-volume-test.c247
-rw-r--r--src/tests/cpulimit-test.c110
-rw-r--r--src/tests/extended-test.c222
-rw-r--r--src/tests/flist-test.c101
-rw-r--r--src/tests/format-test.c169
-rw-r--r--src/tests/get-binary-name-test.c74
-rw-r--r--src/tests/gtk-test.c83
-rw-r--r--src/tests/hashmap-test.c260
-rw-r--r--src/tests/hook-list-test.c61
-rw-r--r--src/tests/interpol-test.c294
-rw-r--r--src/tests/ipacl-test.c116
-rw-r--r--src/tests/json-test.c284
-rw-r--r--src/tests/lfe-filter-test.c196
-rw-r--r--src/tests/lo-latency-test.c188
-rw-r--r--src/tests/lo-test-util.c324
-rw-r--r--src/tests/lo-test-util.h55
-rw-r--r--src/tests/lock-autospawn-test.c129
-rw-r--r--src/tests/mainloop-test.c140
-rw-r--r--src/tests/mcalign-test.c106
-rw-r--r--src/tests/memblock-test.c199
-rw-r--r--src/tests/memblockq-test.c661
-rw-r--r--src/tests/meson.build253
-rw-r--r--src/tests/mix-test.c362
-rw-r--r--src/tests/mult-s16-test.c114
-rw-r--r--src/tests/once-test.c149
-rw-r--r--src/tests/pacat-simple.c114
-rw-r--r--src/tests/parec-simple.c94
-rw-r--r--src/tests/passthrough-test.c347
-rw-r--r--src/tests/proplist-test.c119
-rw-r--r--src/tests/queue-test.c83
-rw-r--r--src/tests/remix-test.c110
-rw-r--r--src/tests/resampler-test.c479
-rw-r--r--src/tests/rtpoll-test.c106
-rw-r--r--src/tests/rtstutter.c128
-rw-r--r--src/tests/runtime-test-util.h56
-rw-r--r--src/tests/sig2str-test.c127
-rw-r--r--src/tests/sigbus-test.c92
-rw-r--r--src/tests/smoother-test.c104
-rw-r--r--src/tests/srbchannel-test.c145
-rw-r--r--src/tests/stripnul.c71
-rw-r--r--src/tests/strlist-test.c70
-rw-r--r--src/tests/sync-playback.c219
-rw-r--r--src/tests/test-daemon.meson.sh9
-rw-r--r--src/tests/thread-mainloop-test.c117
-rw-r--r--src/tests/thread-test.c164
-rw-r--r--src/tests/usergroup-test.c167
-rw-r--r--src/tests/utf8-test.c72
-rw-r--r--src/tests/volume-test.c173
60 files changed, 10425 insertions, 0 deletions
diff --git a/src/tests/alsa-mixer-path-test.c b/src/tests/alsa-mixer-path-test.c
new file mode 100644
index 0000000..75cf086
--- /dev/null
+++ b/src/tests/alsa-mixer-path-test.c
@@ -0,0 +1,120 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <check.h>
+#include <dirent.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+#include <pulse/pulseaudio.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/strlist.h>
+#include <modules/alsa/alsa-mixer.h>
+
+/* This test inspects the Makefile, so this is not applicable when using
+ * Meson. */
+#ifndef MESON_BUILD
+
+/* This function was copied from alsa-mixer.c */
+static const char *get_default_paths_dir(void) {
+ if (pa_run_from_build_tree())
+ return PA_SRCDIR "/modules/alsa/mixer/paths/";
+ else
+ return PA_ALSA_PATHS_DIR;
+}
+
+static pa_strlist *load_makefile() {
+ FILE *f;
+ bool lookforfiles = false;
+ char buf[2048];
+ pa_strlist *result = NULL;
+ const char *Makefile = PA_BUILDDIR "/Makefile";
+
+ f = pa_fopen_cloexec(Makefile, "r");
+ fail_unless(f != NULL); /* Consider skipping this test instead of failing if Makefile not found? */
+ while (!feof(f)) {
+ if (!fgets(buf, sizeof(buf), f)) {
+ fail_unless(feof(f));
+ break;
+ }
+ if (strstr(buf, "dist_alsapaths_DATA = \\") != NULL) {
+ lookforfiles = true;
+ continue;
+ }
+ if (!lookforfiles)
+ continue;
+ if (!strstr(buf, "\\"))
+ lookforfiles = false;
+ else
+ strstr(buf, "\\")[0] = '\0';
+ pa_strip(buf);
+ pa_log_debug("Shipping file '%s'", pa_path_get_filename(buf));
+ result = pa_strlist_prepend(result, pa_path_get_filename(buf));
+ }
+ fclose(f);
+ return result;
+}
+#endif /* end of #ifndef MESON_BUILD */
+
+START_TEST (mixer_path_test) {
+#ifdef MESON_BUILD
+ pa_log_info("Test disabled for meson build");
+ return;
+#else
+ DIR *dir;
+ struct dirent *ent;
+ pa_strlist *ship = load_makefile();
+ const char *pathsdir = get_default_paths_dir();
+ pa_log_debug("Analyzing directory: '%s'", pathsdir);
+
+ dir = opendir(pathsdir);
+ fail_unless(dir != NULL);
+ while ((ent = readdir(dir)) != NULL) {
+ pa_alsa_path *path;
+ if (pa_streq(ent->d_name, ".") || pa_streq(ent->d_name, ".."))
+ continue;
+ pa_log_debug("Analyzing file: '%s'", ent->d_name);
+
+ /* Can the file be parsed? */
+ path = pa_alsa_path_new(pathsdir, ent->d_name, PA_ALSA_DIRECTION_ANY);
+ fail_unless(path != NULL);
+
+ /* Is the file shipped? */
+ if (ship) {
+ pa_strlist *n;
+ bool found = false;
+ for (n = ship; n; n = pa_strlist_next(n))
+ found |= pa_streq(ent->d_name, pa_strlist_data(n));
+ fail_unless(found);
+ }
+ }
+ closedir(dir);
+ pa_strlist_free(ship);
+#endif
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ if (!getenv("MAKE_CHECK"))
+ pa_log_set_level(PA_LOG_DEBUG);
+
+ s = suite_create("Alsa-mixer-path");
+ tc = tcase_create("alsa-mixer-path");
+ tcase_add_test(tc, mixer_path_test);
+ tcase_set_timeout(tc, 30);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/alsa-time-test.c b/src/tests/alsa-time-test.c
new file mode 100644
index 0000000..dff95bb
--- /dev/null
+++ b/src/tests/alsa-time-test.c
@@ -0,0 +1,251 @@
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <assert.h>
+#include <inttypes.h>
+#include <time.h>
+#include <unistd.h>
+#include <pthread.h>
+
+#include <alsa/asoundlib.h>
+
+#define SAMPLE_RATE 44100
+#define CHANNELS 2
+
+static uint64_t timespec_us(const struct timespec *ts) {
+ return
+ ts->tv_sec * 1000000LLU +
+ ts->tv_nsec / 1000LLU;
+}
+
+int main(int argc, char *argv[]) {
+ const char *dev;
+ int r, cap, count = 0;
+ snd_pcm_hw_params_t *hwparams;
+ snd_pcm_sw_params_t *swparams;
+ snd_pcm_status_t *status;
+ snd_pcm_t *pcm;
+ unsigned rate = SAMPLE_RATE;
+ unsigned periods = 2;
+ snd_pcm_uframes_t boundary, buffer_size = SAMPLE_RATE/10; /* 100s */
+ int dir = 1;
+ int fillrate;
+ struct timespec start, last_timestamp = { 0, 0 };
+ uint64_t start_us, last_us = 0;
+ snd_pcm_sframes_t last_avail = 0, last_delay = 0;
+ struct pollfd *pollfds;
+ int n_pollfd;
+ int64_t sample_count = 0;
+ uint16_t *samples;
+ struct sched_param sp;
+
+ r = -1;
+#ifdef _POSIX_PRIORITY_SCHEDULING
+ sp.sched_priority = 5;
+ r = pthread_setschedparam(pthread_self(), SCHED_RR, &sp);
+#endif
+ if (r)
+ printf("Could not get RT prio. :(\n");
+
+ snd_pcm_hw_params_alloca(&hwparams);
+ snd_pcm_sw_params_alloca(&swparams);
+ snd_pcm_status_alloca(&status);
+
+ r = clock_gettime(CLOCK_MONOTONIC, &start);
+ assert(r == 0);
+
+ start_us = timespec_us(&start);
+
+ dev = argc > 1 ? argv[1] : "front:0";
+ cap = argc > 2 ? atoi(argv[2]) : 0;
+ fillrate = argc > 3 ? atoi(argv[3]) : 1;
+ assert(fillrate > 0);
+
+ samples = calloc(fillrate, CHANNELS*sizeof(uint16_t));
+ assert(samples);
+
+ if (cap == 0)
+ r = snd_pcm_open(&pcm, dev, SND_PCM_STREAM_PLAYBACK, 0);
+ else
+ r = snd_pcm_open(&pcm, dev, SND_PCM_STREAM_CAPTURE, 0);
+ assert(r == 0);
+
+ r = snd_pcm_hw_params_any(pcm, hwparams);
+ assert(r == 0);
+
+ r = snd_pcm_hw_params_set_rate_resample(pcm, hwparams, 0);
+ assert(r == 0);
+
+ r = snd_pcm_hw_params_set_access(pcm, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
+ assert(r == 0);
+
+ r = snd_pcm_hw_params_set_format(pcm, hwparams, SND_PCM_FORMAT_S16_LE);
+ assert(r == 0);
+
+ r = snd_pcm_hw_params_set_rate_near(pcm, hwparams, &rate, NULL);
+ assert(r == 0);
+
+ r = snd_pcm_hw_params_set_channels(pcm, hwparams, CHANNELS);
+ assert(r == 0);
+
+ r = snd_pcm_hw_params_set_periods_integer(pcm, hwparams);
+ assert(r == 0);
+
+ r = snd_pcm_hw_params_set_periods_near(pcm, hwparams, &periods, &dir);
+ assert(r == 0);
+
+ r = snd_pcm_hw_params_set_buffer_size_near(pcm, hwparams, &buffer_size);
+ assert(r == 0);
+
+ r = snd_pcm_hw_params(pcm, hwparams);
+ assert(r == 0);
+
+ r = snd_pcm_hw_params_current(pcm, hwparams);
+ assert(r == 0);
+
+ r = snd_pcm_sw_params_current(pcm, swparams);
+ assert(r == 0);
+
+ if (cap == 0)
+ r = snd_pcm_sw_params_set_avail_min(pcm, swparams, 1);
+ else
+ r = snd_pcm_sw_params_set_avail_min(pcm, swparams, 0);
+ assert(r == 0);
+
+ r = snd_pcm_sw_params_set_period_event(pcm, swparams, 0);
+ assert(r == 0);
+
+ r = snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size);
+ assert(r == 0);
+ r = snd_pcm_sw_params_set_start_threshold(pcm, swparams, buffer_size - (buffer_size % fillrate));
+ assert(r == 0);
+
+ r = snd_pcm_sw_params_get_boundary(swparams, &boundary);
+ assert(r == 0);
+ r = snd_pcm_sw_params_set_stop_threshold(pcm, swparams, boundary);
+ assert(r == 0);
+
+ r = snd_pcm_sw_params_set_tstamp_mode(pcm, swparams, SND_PCM_TSTAMP_ENABLE);
+ assert(r == 0);
+
+ r = snd_pcm_sw_params(pcm, swparams);
+ assert(r == 0);
+
+ r = snd_pcm_sw_params_current(pcm, swparams);
+ assert(r == 0);
+
+/* assert(snd_pcm_hw_params_is_monotonic(hwparams) > 0); */
+
+ n_pollfd = snd_pcm_poll_descriptors_count(pcm);
+ assert(n_pollfd > 0);
+
+ pollfds = malloc(sizeof(struct pollfd) * n_pollfd);
+ assert(pollfds);
+
+ r = snd_pcm_poll_descriptors(pcm, pollfds, n_pollfd);
+ assert(r == n_pollfd);
+
+ printf("Starting. Buffer size is %u frames\n", (unsigned int) buffer_size);
+
+ if (cap) {
+ r = snd_pcm_start(pcm);
+ assert(r == 0);
+ }
+
+ for (;;) {
+ snd_pcm_sframes_t avail, delay;
+ struct timespec now, timestamp;
+ unsigned short revents;
+ int handled = 0;
+ uint64_t now_us, timestamp_us;
+ snd_pcm_state_t state;
+ unsigned long long pos;
+
+ r = poll(pollfds, n_pollfd, 0);
+ assert(r >= 0);
+
+ r = snd_pcm_poll_descriptors_revents(pcm, pollfds, n_pollfd, &revents);
+ assert(r == 0);
+
+ if (cap == 0)
+ assert((revents & ~POLLOUT) == 0);
+ else
+ assert((revents & ~POLLIN) == 0);
+
+ avail = snd_pcm_avail(pcm);
+ assert(avail >= 0);
+
+ r = snd_pcm_status(pcm, status);
+ assert(r == 0);
+
+ /* This assertion fails from time to time. ALSA seems to be broken */
+/* assert(avail == (snd_pcm_sframes_t) snd_pcm_status_get_avail(status)); */
+/* printf("%lu %lu\n", (unsigned long) avail, (unsigned long) snd_pcm_status_get_avail(status)); */
+
+ snd_pcm_status_get_htstamp(status, &timestamp);
+ delay = snd_pcm_status_get_delay(status);
+ state = snd_pcm_status_get_state(status);
+
+ r = clock_gettime(CLOCK_MONOTONIC, &now);
+ assert(r == 0);
+
+ assert(!revents || avail > 0);
+
+ if ((!cap && (avail >= fillrate)) || (cap && (unsigned)avail >= buffer_size)) {
+ snd_pcm_sframes_t sframes;
+
+ if (cap == 0)
+ sframes = snd_pcm_writei(pcm, samples, fillrate);
+ else
+ sframes = snd_pcm_readi(pcm, samples, fillrate);
+ assert(sframes == fillrate);
+
+ handled = fillrate;
+ sample_count += fillrate;
+ }
+
+ if (!handled &&
+ memcmp(&timestamp, &last_timestamp, sizeof(timestamp)) == 0 &&
+ avail == last_avail &&
+ delay == last_delay) {
+ /* This is boring */
+ continue;
+ }
+
+ now_us = timespec_us(&now);
+ timestamp_us = timespec_us(&timestamp);
+
+ if (cap == 0)
+ pos = (unsigned long long) ((sample_count - handled - delay) * 1000000LU / SAMPLE_RATE);
+ else
+ pos = (unsigned long long) ((sample_count - handled + delay) * 1000000LU / SAMPLE_RATE);
+
+ if (count++ % 50 == 0)
+ printf("Elapsed\tCPU\tALSA\tPos\tSamples\tavail\tdelay\trevents\thandled\tstate\n");
+
+ printf("%llu\t%llu\t%llu\t%llu\t%llu\t%li\t%li\t%i\t%i\t%i\n",
+ (unsigned long long) (now_us - last_us),
+ (unsigned long long) (now_us - start_us),
+ (unsigned long long) (timestamp_us ? timestamp_us - start_us : 0),
+ pos,
+ (unsigned long long) sample_count,
+ (signed long) avail,
+ (signed long) delay,
+ revents,
+ handled,
+ state);
+
+ if (cap == 0)
+ /** When this assert is hit, most likely something bad
+ * happened, i.e. the avail jumped suddenly. */
+ assert((unsigned) avail <= buffer_size);
+
+ last_avail = avail;
+ last_delay = delay;
+ last_timestamp = timestamp;
+ last_us = now_us;
+ }
+
+ return 0;
+}
diff --git a/src/tests/asyncmsgq-test.c b/src/tests/asyncmsgq-test.c
new file mode 100644
index 0000000..219c020
--- /dev/null
+++ b/src/tests/asyncmsgq-test.c
@@ -0,0 +1,125 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <check.h>
+
+#include <pulsecore/asyncmsgq.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+enum {
+ OPERATION_A,
+ OPERATION_B,
+ OPERATION_C,
+ QUIT
+};
+
+static void the_thread(void *_q) {
+ pa_asyncmsgq *q = _q;
+ int quit = 0;
+
+ do {
+ int code = 0;
+
+ pa_assert_se(pa_asyncmsgq_get(q, NULL, &code, NULL, NULL, NULL, 1) == 0);
+
+ switch (code) {
+
+ case OPERATION_A:
+ pa_log_info("Operation A");
+ break;
+
+ case OPERATION_B:
+ pa_log_info("Operation B");
+ break;
+
+ case OPERATION_C:
+ pa_log_info("Operation C");
+ break;
+
+ case QUIT:
+ pa_log_info("quit");
+ quit = 1;
+ break;
+ }
+
+ pa_asyncmsgq_done(q, 0);
+
+ } while (!quit);
+}
+
+START_TEST (asyncmsgq_test) {
+ pa_asyncmsgq *q;
+ pa_thread *t;
+
+ q = pa_asyncmsgq_new(0);
+ fail_unless(q != NULL);
+
+ t = pa_thread_new("test", the_thread, q);
+ fail_unless(t != NULL);
+
+ pa_log_info("Operation A post");
+ pa_asyncmsgq_post(q, NULL, OPERATION_A, NULL, 0, NULL, NULL);
+
+ pa_thread_yield();
+
+ pa_log_info("Operation B post");
+ pa_asyncmsgq_post(q, NULL, OPERATION_B, NULL, 0, NULL, NULL);
+
+ pa_thread_yield();
+
+ pa_log_info("Operation C send");
+ pa_asyncmsgq_send(q, NULL, OPERATION_C, NULL, 0, NULL);
+
+ pa_thread_yield();
+
+ pa_log_info("Quit post");
+ pa_asyncmsgq_post(q, NULL, QUIT, NULL, 0, NULL, NULL);
+
+ pa_thread_free(t);
+
+ pa_asyncmsgq_unref(q);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ s = suite_create("Async Message Queue");
+ tc = tcase_create("asyncmsgq");
+ tcase_add_test(tc, asyncmsgq_test);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/asyncq-test.c b/src/tests/asyncq-test.c
new file mode 100644
index 0000000..51a557a
--- /dev/null
+++ b/src/tests/asyncq-test.c
@@ -0,0 +1,107 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <check.h>
+
+#include <pulse/util.h>
+#include <pulsecore/asyncq.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+static void producer(void *_q) {
+ pa_asyncq *q = _q;
+ int i;
+
+ for (i = 0; i < 1000; i++) {
+ pa_log_debug("pushing %i", i);
+ pa_asyncq_push(q, PA_UINT_TO_PTR(i+1), 1);
+ }
+
+ pa_asyncq_push(q, PA_UINT_TO_PTR(-1), true);
+ pa_log_debug("pushed end");
+}
+
+static void consumer(void *_q) {
+ pa_asyncq *q = _q;
+ void *p;
+ int i;
+
+ pa_msleep(1000);
+
+ for (i = 0;; i++) {
+ p = pa_asyncq_pop(q, true);
+
+ if (p == PA_UINT_TO_PTR(-1))
+ break;
+
+ fail_unless(p == PA_UINT_TO_PTR(i+1));
+
+ pa_log_debug("popped %i", i);
+ }
+
+ pa_log_debug("popped end");
+}
+
+START_TEST (asyncq_test) {
+ pa_asyncq *q;
+ pa_thread *t1, *t2;
+
+ if (!getenv("MAKE_CHECK"))
+ pa_log_set_level(PA_LOG_DEBUG);
+
+ q = pa_asyncq_new(0);
+ fail_unless(q != NULL);
+
+ t1 = pa_thread_new("producer", producer, q);
+ fail_unless(t1 != NULL);
+ t2 = pa_thread_new("consumer", consumer, q);
+ fail_unless(t2 != NULL);
+
+ pa_thread_free(t1);
+ pa_thread_free(t2);
+
+ pa_asyncq_free(q, NULL);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ s = suite_create("Async Queue");
+ tc = tcase_create("asyncq");
+ tcase_add_test(tc, asyncq_test);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/atomic-test.c b/src/tests/atomic-test.c
new file mode 100644
index 0000000..eb986e7
--- /dev/null
+++ b/src/tests/atomic-test.c
@@ -0,0 +1,135 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2014 David Henningsson, Canonical Ltd.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+/* This test spawns two threads on distinct cpu-cores that pass a value
+ * between each other through shared memory protected by pa_atomic_t.
+ * Thread "left" continuously increments a value and writes its contents to memory.
+ * Thread "right" continuously reads the value and checks whether it was incremented.
+ *
+ * With the pa_atomic_load/pa_atomic_store implementations based on __sync_synchronize,
+ * this will fail after some time (sometimes 2 seconds, sometimes 8 hours) at least
+ * on ARM Cortex-A53 and ARM Cortex-A57 systems.
+ *
+ * On x86_64, it does not.
+ *
+ * The chosen implementation in some way mimics a situation that can also occur
+ * using memfd srbchannel transport.
+ *
+ * NOTE: This is a long-running test, so don't execute in normal test suite.
+ *
+ * */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <check.h>
+
+#include <pulsecore/thread.h>
+#include <pulse/rtclock.h>
+#include <pulse/xmalloc.h>
+#include <pulsecore/semaphore.h>
+#include <pthread.h>
+#include <pulsecore/atomic.h>
+
+#define MEMORY_SIZE (8 * 2 * 1024 * 1024)
+
+
+typedef struct io_t {
+ pa_atomic_t *flag;
+ char* memory;
+ cpu_set_t cpuset;
+} io_t;
+
+static void read_func(void* data) {
+ io_t *io = (io_t *) data;
+ size_t expect = 0;
+ size_t value = 0;
+ pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &io->cpuset);
+ while(1) {
+ if(pa_atomic_load(io->flag) == 1) {
+ memcpy(&value, io->memory, sizeof(value));
+ pa_atomic_sub(io->flag, 1);
+ ck_assert_uint_eq(value, expect);
+ ++expect;
+ }
+ }
+}
+
+static void write_func(void* data) {
+ io_t *io = (io_t *) data;
+ size_t value = 0;
+ pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &io->cpuset);
+ while(1) {
+ if(pa_atomic_load(io->flag) == 0) {
+ memcpy(io->memory, &value, sizeof(value));
+ pa_atomic_add(io->flag, 1);
+ ++value;
+ }
+ }
+}
+
+START_TEST (atomic_test) {
+ pa_thread *thread1, *thread2;
+ io_t io1, io2;
+
+ char* memory = pa_xmalloc0(MEMORY_SIZE);
+ pa_atomic_t flag = PA_ATOMIC_INIT(0);
+ memset(memory, 0, MEMORY_SIZE);
+
+ /* intentionally misalign memory since srbchannel also does not
+ * always read/write aligned. Might be a red hering. */
+ io1.memory = io2.memory = memory + 1025;
+ io1.flag = io2.flag = &flag;
+
+ CPU_ZERO(&io1.cpuset);
+ CPU_SET(1, &io1.cpuset);
+ thread1 = pa_thread_new("left", &write_func, &io1);
+
+ CPU_ZERO(&io2.cpuset);
+ CPU_SET(3, &io2.cpuset);
+ thread2 = pa_thread_new("right", &read_func, &io2);
+ pa_thread_free(thread1);
+ pa_thread_free(thread2);
+ pa_xfree(memory);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ if (!getenv("MAKE_CHECK"))
+ pa_log_set_level(PA_LOG_DEBUG);
+
+ s = suite_create("atomic");
+ tc = tcase_create("atomic");
+ tcase_add_test(tc, atomic_test);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/channelmap-test.c b/src/tests/channelmap-test.c
new file mode 100644
index 0000000..eb0187c
--- /dev/null
+++ b/src/tests/channelmap-test.c
@@ -0,0 +1,58 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+
+#include <check.h>
+
+#include <pulse/channelmap.h>
+
+START_TEST (channelmap_test) {
+ char cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+ pa_channel_map map, map2;
+
+ pa_channel_map_init_auto(&map, 6, PA_CHANNEL_MAP_AIFF);
+
+ fprintf(stderr, "map: <%s>\n", pa_channel_map_snprint(cm, sizeof(cm), &map));
+
+ pa_channel_map_init_auto(&map, 6, PA_CHANNEL_MAP_AUX);
+
+ fprintf(stderr, "map: <%s>\n", pa_channel_map_snprint(cm, sizeof(cm), &map));
+
+ pa_channel_map_init_auto(&map, 6, PA_CHANNEL_MAP_ALSA);
+
+ fprintf(stderr, "map: <%s>\n", pa_channel_map_snprint(cm, sizeof(cm), &map));
+
+ pa_channel_map_init_extend(&map, 14, PA_CHANNEL_MAP_ALSA);
+
+ fprintf(stderr, "map: <%s>\n", pa_channel_map_snprint(cm, sizeof(cm), &map));
+
+ pa_channel_map_parse(&map2, cm);
+
+ fail_unless(pa_channel_map_equal(&map, &map2));
+
+ pa_channel_map_parse(&map2, "left,test");
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ s = suite_create("Channel Map");
+ tc = tcase_create("channelmap");
+ tcase_add_test(tc, channelmap_test);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/close-test.c b/src/tests/close-test.c
new file mode 100644
index 0000000..7a6fec5
--- /dev/null
+++ b/src/tests/close-test.c
@@ -0,0 +1,20 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <pulsecore/core-util.h>
+
+int main(int argc, char *argv[]) {
+
+ open("/dev/null", O_RDONLY);
+ open("/dev/null", O_RDONLY);
+ open("/dev/null", O_RDONLY);
+ open("/dev/null", O_RDONLY);
+
+ pa_close_all(5, -1);
+
+ return 0;
+}
diff --git a/src/tests/connect-stress.c b/src/tests/connect-stress.c
new file mode 100644
index 0000000..a243df9
--- /dev/null
+++ b/src/tests/connect-stress.c
@@ -0,0 +1,235 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <check.h>
+
+#include <pulse/pulseaudio.h>
+#include <pulse/mainloop.h>
+
+#include <pulsecore/sink.h>
+
+/* Set the number of streams such that it allows two simultaneous instances of
+ * connect-stress to be run and not go above the max limit for streams-per-sink.
+ * This leaves enough room for a couple other streams from regular system usage,
+ * which makes a non-error abort less likely (although still easily possible of
+ * playing >=3 streams outside of the test - including internal loopback, rtp,
+ * combine, remap streams etc.) */
+/* #define NSTREAMS ((PA_MAX_INPUTS_PER_SINK/2) - 1) */
+
+/* This test broke when PA_MAX_INPUTS_PER_SINK was increased from 32 to 256.
+ * Because we currently don't have time to figure out why, let's just set
+ * NSTREAMS to 20 in the meantime.
+ */
+#define NSTREAMS 20
+#define NTESTS 1000
+#define SAMPLE_HZ 44100
+
+static pa_context *context = NULL;
+static pa_stream *streams[NSTREAMS];
+static pa_threaded_mainloop *mainloop = NULL;
+static char *bname;
+
+static const pa_sample_spec sample_spec = {
+ .format = PA_SAMPLE_FLOAT32,
+ .rate = SAMPLE_HZ,
+ .channels = 1
+};
+
+static void context_state_callback(pa_context *c, void *userdata);
+
+/* Note: don't conflict with connect(2) declaration */
+static void _connect(const char *name, int *try) {
+ int ret;
+ pa_mainloop_api *api;
+
+ /* Set up a new main loop */
+ mainloop = pa_threaded_mainloop_new();
+ fail_unless(mainloop != NULL);
+
+ api = pa_threaded_mainloop_get_api(mainloop);
+ context = pa_context_new(api, name);
+ fail_unless(context != NULL);
+
+ pa_context_set_state_callback(context, context_state_callback, try);
+
+ /* Connect the context */
+ if (pa_context_connect(context, NULL, 0, NULL) < 0) {
+ fprintf(stderr, "pa_context_connect() failed.\n");
+ ck_abort();
+ }
+
+ ret = pa_threaded_mainloop_start(mainloop);
+ fail_unless(ret == 0);
+}
+
+static void _disconnect(void) {
+ int i;
+
+ fail_unless(mainloop != NULL);
+ fail_unless(context != NULL);
+
+ pa_threaded_mainloop_lock(mainloop);
+
+ for (i = 0; i < NSTREAMS; i++)
+ if (streams[i]) {
+ pa_stream_disconnect(streams[i]);
+ pa_stream_unref(streams[i]);
+ streams[i] = NULL;
+ }
+
+ pa_context_disconnect(context);
+ context = NULL;
+
+ pa_threaded_mainloop_unlock(mainloop);
+ pa_threaded_mainloop_stop(mainloop);
+ pa_threaded_mainloop_free(mainloop);
+ mainloop = NULL;
+}
+
+static const pa_buffer_attr buffer_attr = {
+ .maxlength = SAMPLE_HZ * sizeof(float) * NSTREAMS,
+ .tlength = (uint32_t) -1,
+ .prebuf = 0, /* Setting prebuf to 0 guarantees us the streams will run synchronously, no matter what */
+ .minreq = (uint32_t) -1,
+ .fragsize = 0
+};
+
+static void stream_write_callback(pa_stream *stream, size_t nbytes, void *userdata) {
+ char silence[8192];
+
+ memset(silence, 0, sizeof(silence));
+
+ while (nbytes) {
+ int n = PA_MIN(sizeof(silence), nbytes);
+ pa_stream_write(stream, silence, n, NULL, 0, 0);
+ nbytes -= n;
+ }
+}
+
+static void stream_state_callback(pa_stream *s, void *userdata) {
+ fail_unless(s != NULL);
+
+ switch (pa_stream_get_state(s)) {
+ case PA_STREAM_UNCONNECTED:
+ case PA_STREAM_CREATING:
+ case PA_STREAM_TERMINATED:
+ case PA_STREAM_READY:
+ break;
+
+ default:
+ case PA_STREAM_FAILED:
+ fprintf(stderr, "Stream error: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
+ ck_abort();
+ }
+}
+
+static void context_state_callback(pa_context *c, void *userdata) {
+ int *try;
+
+ fail_unless(c != NULL);
+ fail_unless(userdata != NULL);
+
+ try = (int*)userdata;
+
+ switch (pa_context_get_state(c)) {
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+
+ case PA_CONTEXT_READY: {
+
+ int i;
+ fprintf(stderr, "Connection (%d of %d) established.\n", (*try)+1, NTESTS);
+
+ for (i = 0; i < NSTREAMS; i++) {
+ char name[64];
+
+ snprintf(name, sizeof(name), "stream #%i", i);
+ streams[i] = pa_stream_new(c, name, &sample_spec, NULL);
+ fail_unless(streams[i] != NULL);
+ pa_stream_set_state_callback(streams[i], stream_state_callback, NULL);
+ pa_stream_set_write_callback(streams[i], stream_write_callback, NULL);
+ pa_stream_connect_playback(streams[i], NULL, &buffer_attr, 0, NULL, NULL);
+ }
+
+ break;
+ }
+
+ case PA_CONTEXT_TERMINATED:
+ fprintf(stderr, "Connection terminated.\n");
+ pa_context_unref(context);
+ context = NULL;
+ break;
+
+ case PA_CONTEXT_FAILED:
+ default:
+ fprintf(stderr, "Context error: %s\n", pa_strerror(pa_context_errno(c)));
+ ck_abort();
+ }
+}
+
+START_TEST (connect_stress_test) {
+ int i;
+
+ for (i = 0; i < NSTREAMS; i++)
+ streams[i] = NULL;
+
+ for (i = 0; i < NTESTS; i++) {
+ _connect(bname, &i);
+ usleep(rand() % 500000);
+ _disconnect();
+ usleep(rand() % 500000);
+ }
+
+ fprintf(stderr, "Done.\n");
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ bname = argv[0];
+
+ s = suite_create("Connect Stress");
+ tc = tcase_create("connectstress");
+ tcase_add_test(tc, connect_stress_test);
+ tcase_set_timeout(tc, 20 * 60);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/core-util-test.c b/src/tests/core-util-test.c
new file mode 100644
index 0000000..8d1db0c
--- /dev/null
+++ b/src/tests/core-util-test.c
@@ -0,0 +1,284 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <signal.h>
+
+#include <check.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/core-util.h>
+
+START_TEST (modargs_test_parse_boolean) {
+ ck_assert_int_eq(pa_parse_boolean("true"), true);
+ ck_assert_int_eq(pa_parse_boolean("yes"), true);
+ ck_assert_int_eq(pa_parse_boolean("1"), true);
+
+ ck_assert_int_eq(pa_parse_boolean("false"), false);
+ ck_assert_int_eq(pa_parse_boolean("no"), false);
+ ck_assert_int_eq(pa_parse_boolean("0"), false);
+
+ ck_assert_int_eq(pa_parse_boolean("maybe"), -1);
+ ck_assert_int_eq(pa_parse_boolean("42"), -1);
+}
+END_TEST
+
+START_TEST (modargs_test_parse_volume) {
+ pa_volume_t value;
+
+ // dB volumes
+ ck_assert_int_eq(pa_parse_volume("-20dB", &value), 0);
+ ck_assert_int_eq(value, 30419);
+ ck_assert_int_eq(pa_parse_volume("-10dB", &value), 0);
+ ck_assert_int_eq(value, 44649);
+ ck_assert_int_eq(pa_parse_volume("-1dB", &value), 0);
+ ck_assert_int_eq(value, 63069);
+ ck_assert_int_eq(pa_parse_volume("0dB", &value), 0);
+ ck_assert_int_eq(value, 65536);
+ ck_assert_int_eq(pa_parse_volume("1dB", &value), 0);
+ ck_assert_int_eq(value, 68100);
+ ck_assert_int_eq(pa_parse_volume("10dB", &value), 0);
+ ck_assert_int_eq(value, 96194);
+
+ // lowercase db
+ ck_assert_int_eq(pa_parse_volume("10db", &value), 0);
+ ck_assert_int_eq(value, 96194);
+
+ // percentage volumes
+ ck_assert_int_eq(pa_parse_volume("0%", &value), 0);
+ ck_assert_int_eq(value, 0);
+ ck_assert_int_eq(pa_parse_volume("50%", &value), 0);
+ ck_assert_int_eq(value, 32768);
+ ck_assert_int_eq(pa_parse_volume("100%", &value), 0);
+ ck_assert_int_eq(value, 65536);
+ ck_assert_int_eq(pa_parse_volume("150%", &value), 0);
+ ck_assert_int_eq(value, 98304);
+
+ // integer volumes`
+ ck_assert_int_eq(pa_parse_volume("0", &value), 0);
+ ck_assert_int_eq(value, 0);
+ ck_assert_int_eq(pa_parse_volume("100", &value), 0);
+ ck_assert_int_eq(value, 100);
+ ck_assert_int_eq(pa_parse_volume("1000", &value), 0);
+ ck_assert_int_eq(value, 1000);
+ ck_assert_int_eq(pa_parse_volume("65536", &value), 0);
+ ck_assert_int_eq(value, 65536);
+ ck_assert_int_eq(pa_parse_volume("100000", &value), 0);
+ ck_assert_int_eq(value, 100000);
+
+ // invalid volumes
+ ck_assert_int_lt(pa_parse_volume("", &value), 0);
+ ck_assert_int_lt(pa_parse_volume("-2", &value), 0);
+ ck_assert_int_lt(pa_parse_volume("on", &value), 0);
+ ck_assert_int_lt(pa_parse_volume("off", &value), 0);
+ ck_assert_int_lt(pa_parse_volume("none", &value), 0);
+}
+END_TEST
+
+START_TEST (modargs_test_atoi) {
+ int32_t value;
+
+ // decimal
+ ck_assert_int_eq(pa_atoi("100000", &value), 0);
+ ck_assert_int_eq(value, 100000);
+ ck_assert_int_eq(pa_atoi("-100000", &value), 0);
+ ck_assert_int_eq(value, -100000);
+
+ // hexadecimal
+ ck_assert_int_eq(pa_atoi("0x100000", &value), 0);
+ ck_assert_int_eq(value, 0x100000);
+ ck_assert_int_eq(pa_atoi("-0x100000", &value), 0);
+ ck_assert_int_eq(value, -0x100000);
+
+ // invalid values
+ ck_assert_int_lt(pa_atoi("3.14", &value), 0);
+ ck_assert_int_lt(pa_atoi("7*8", &value), 0);
+ ck_assert_int_lt(pa_atoi("false", &value), 0);
+}
+END_TEST
+
+START_TEST (modargs_test_atou) {
+ uint32_t value;
+
+ // decimal
+ ck_assert_int_eq(pa_atou("100000", &value), 0);
+ ck_assert_int_eq(value, 100000);
+
+ // hexadecimal
+ ck_assert_int_eq(pa_atou("0x100000", &value), 0);
+ ck_assert_int_eq(value, 0x100000);
+
+ // invalid values
+ ck_assert_int_lt(pa_atou("-100000", &value), 0);
+ ck_assert_int_lt(pa_atou("-0x100000", &value), 0);
+ ck_assert_int_lt(pa_atou("3.14", &value), 0);
+ ck_assert_int_lt(pa_atou("7*8", &value), 0);
+ ck_assert_int_lt(pa_atou("false", &value), 0);
+}
+END_TEST
+
+START_TEST (modargs_test_atol) {
+ long value;
+
+ // decimal
+ ck_assert_int_eq(pa_atol("100000", &value), 0);
+ ck_assert_int_eq(value, 100000l);
+ ck_assert_int_eq(pa_atol("-100000", &value), 0);
+ ck_assert_int_eq(value, -100000l);
+
+ // hexadecimal
+ ck_assert_int_eq(pa_atol("0x100000", &value), 0);
+ ck_assert_int_eq(value, 0x100000l);
+ ck_assert_int_eq(pa_atol("-0x100000", &value), 0);
+ ck_assert_int_eq(value, -0x100000l);
+
+ // invalid values
+ ck_assert_int_lt(pa_atol("3.14", &value), 0);
+ ck_assert_int_lt(pa_atol("7*8", &value), 0);
+ ck_assert_int_lt(pa_atol("false", &value), 0);
+}
+END_TEST
+
+START_TEST (modargs_test_atod) {
+ double value;
+ double epsilon = 0.001;
+
+ // decimal
+ ck_assert_int_eq(pa_atod("100000", &value), 0);
+ ck_assert(value > 100000 - epsilon);
+ ck_assert(value < 100000 + epsilon);
+ ck_assert_int_eq(pa_atod("-100000", &value), 0);
+ ck_assert(value > -100000 - epsilon);
+ ck_assert(value < -100000 + epsilon);
+ ck_assert_int_eq(pa_atod("3.14", &value), 0);
+ ck_assert(value > 3.14 - epsilon);
+ ck_assert(value < 3.14 + epsilon);
+
+ // invalid values
+ ck_assert_int_lt(pa_atod("7*8", &value), 0);
+ ck_assert_int_lt(pa_atod("false", &value), 0);
+}
+END_TEST
+
+START_TEST (modargs_test_replace) {
+ char* value;
+
+ value = pa_replace("abcde", "bcd", "XYZ");
+ ck_assert_str_eq(value, "aXYZe");
+ pa_xfree(value);
+
+ value = pa_replace("abe", "b", "bab");
+ ck_assert_str_eq(value, "ababe");
+ pa_xfree(value);
+
+ value = pa_replace("abe", "c", "bab");
+ ck_assert_str_eq(value, "abe");
+ pa_xfree(value);
+
+ value = pa_replace("abcde", "bcd", "");
+ ck_assert_str_eq(value, "ae");
+ pa_xfree(value);
+}
+END_TEST
+
+START_TEST (modargs_test_replace_fail_1) {
+ pa_replace(NULL, "b", "bab");
+}
+END_TEST
+
+START_TEST (modargs_test_replace_fail_2) {
+ pa_replace("abe", NULL, "bab");
+}
+END_TEST
+
+START_TEST (modargs_test_replace_fail_3) {
+ pa_replace("abcde", "b", NULL);
+}
+END_TEST
+
+START_TEST (modargs_test_escape) {
+ char* value;
+
+ value = pa_escape("abcde", "bcd");
+ ck_assert_str_eq(value, "a\\b\\c\\de");
+ pa_xfree(value);
+
+ value = pa_escape("\\", "bcd");
+ ck_assert_str_eq(value, "\\\\");
+ pa_xfree(value);
+
+ value = pa_escape("\\", NULL);
+ ck_assert_str_eq(value, "\\\\");
+ pa_xfree(value);
+}
+END_TEST
+
+START_TEST (modargs_test_replace_fail_4) {
+ pa_replace("abe", "", "bab");
+}
+END_TEST
+
+START_TEST (modargs_test_unescape) {
+ char* value;
+
+ value = pa_unescape(pa_xstrdup("a\\b\\c\\de"));
+ ck_assert_str_eq(value, "abcde");
+ pa_xfree(value);
+
+ value = pa_unescape(pa_xstrdup("\\\\"));
+ ck_assert_str_eq(value, "\\");
+ pa_xfree(value);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ if (!getenv("MAKE_CHECK"))
+ pa_log_set_level(PA_LOG_DEBUG);
+
+ s = suite_create("Core-Util");
+
+ tc = tcase_create("core-util");
+ suite_add_tcase(s, tc);
+ tcase_add_test(tc, modargs_test_parse_boolean);
+ tcase_add_test(tc, modargs_test_parse_volume);
+ tcase_add_test(tc, modargs_test_atoi);
+ tcase_add_test(tc, modargs_test_atou);
+ tcase_add_test(tc, modargs_test_atol);
+ tcase_add_test(tc, modargs_test_atod);
+ tcase_add_test(tc, modargs_test_replace);
+ tcase_add_test_raise_signal(tc, modargs_test_replace_fail_1, SIGABRT);
+ tcase_add_test_raise_signal(tc, modargs_test_replace_fail_2, SIGABRT);
+ tcase_add_test_raise_signal(tc, modargs_test_replace_fail_3, SIGABRT);
+ tcase_add_test_raise_signal(tc, modargs_test_replace_fail_4, SIGABRT);
+ tcase_add_test(tc, modargs_test_escape);
+ tcase_add_test(tc, modargs_test_unescape);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/cpu-mix-test.c b/src/tests/cpu-mix-test.c
new file mode 100644
index 0000000..cad8984
--- /dev/null
+++ b/src/tests/cpu-mix-test.c
@@ -0,0 +1,224 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <check.h>
+
+#include <pulsecore/cpu.h>
+#include <pulsecore/cpu-arm.h>
+#include <pulsecore/random.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/mix.h>
+
+#include "runtime-test-util.h"
+
+#define SAMPLES 1028
+#define TIMES 1000
+#define TIMES2 100
+
+static void acquire_mix_streams(pa_mix_info streams[], unsigned nstreams) {
+ unsigned i;
+
+ for (i = 0; i < nstreams; i++)
+ streams[i].ptr = pa_memblock_acquire_chunk(&streams[i].chunk);
+}
+
+static void release_mix_streams(pa_mix_info streams[], unsigned nstreams) {
+ unsigned i;
+
+ for (i = 0; i < nstreams; i++)
+ pa_memblock_release(streams[i].chunk.memblock);
+}
+
+static void run_mix_test(
+ pa_do_mix_func_t func,
+ pa_do_mix_func_t orig_func,
+ int align,
+ int channels,
+ bool correct,
+ bool perf) {
+
+ PA_DECLARE_ALIGNED(8, int16_t, in0[SAMPLES * 4]) = { 0 };
+ PA_DECLARE_ALIGNED(8, int16_t, in1[SAMPLES * 4]) = { 0 };
+ PA_DECLARE_ALIGNED(8, int16_t, out[SAMPLES * 4]) = { 0 };
+ PA_DECLARE_ALIGNED(8, int16_t, out_ref[SAMPLES * 4]) = { 0 };
+ int16_t *samples0, *samples1;
+ int16_t *samples, *samples_ref;
+ int nsamples;
+ pa_mempool *pool;
+ pa_memchunk c0, c1;
+ pa_mix_info m[2];
+ int i;
+
+ pa_assert(channels == 1 || channels == 2 || channels == 4);
+
+ /* Force sample alignment as requested */
+ samples0 = in0 + (8 - align);
+ samples1 = in1 + (8 - align);
+ samples = out + (8 - align);
+ samples_ref = out_ref + (8 - align);
+ nsamples = channels * (SAMPLES - (8 - align));
+
+ fail_unless((pool = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true)) != NULL, NULL);
+
+ pa_random(samples0, nsamples * sizeof(int16_t));
+ c0.memblock = pa_memblock_new_fixed(pool, samples0, nsamples * sizeof(int16_t), false);
+ c0.length = pa_memblock_get_length(c0.memblock);
+ c0.index = 0;
+
+ pa_random(samples1, nsamples * sizeof(int16_t));
+ c1.memblock = pa_memblock_new_fixed(pool, samples1, nsamples * sizeof(int16_t), false);
+ c1.length = pa_memblock_get_length(c1.memblock);
+ c1.index = 0;
+
+ m[0].chunk = c0;
+ m[0].volume.channels = channels;
+ for (i = 0; i < channels; i++) {
+ m[0].volume.values[i] = PA_VOLUME_NORM;
+ m[0].linear[i].i = 0x5555;
+ }
+
+ m[1].chunk = c1;
+ m[1].volume.channels = channels;
+ for (i = 0; i < channels; i++) {
+ m[1].volume.values[i] = PA_VOLUME_NORM;
+ m[1].linear[i].i = 0x6789;
+ }
+
+ if (correct) {
+ acquire_mix_streams(m, 2);
+ orig_func(m, 2, channels, samples_ref, nsamples * sizeof(int16_t));
+ release_mix_streams(m, 2);
+
+ acquire_mix_streams(m, 2);
+ func(m, 2, channels, samples, nsamples * sizeof(int16_t));
+ release_mix_streams(m, 2);
+
+ for (i = 0; i < nsamples; i++) {
+ if (samples[i] != samples_ref[i]) {
+ pa_log_debug("Correctness test failed: align=%d, channels=%d", align, channels);
+ pa_log_debug("%d: %hd != %04hd (%hd + %hd)",
+ i,
+ samples[i], samples_ref[i],
+ samples0[i], samples1[i]);
+ ck_abort();
+ }
+ }
+ }
+
+ if (perf) {
+ pa_log_debug("Testing %d-channel mixing performance with %d sample alignment", channels, align);
+
+ PA_RUNTIME_TEST_RUN_START("func", TIMES, TIMES2) {
+ acquire_mix_streams(m, 2);
+ func(m, 2, channels, samples, nsamples * sizeof(int16_t));
+ release_mix_streams(m, 2);
+ } PA_RUNTIME_TEST_RUN_STOP
+
+ PA_RUNTIME_TEST_RUN_START("orig", TIMES, TIMES2) {
+ acquire_mix_streams(m, 2);
+ orig_func(m, 2, channels, samples_ref, nsamples * sizeof(int16_t));
+ release_mix_streams(m, 2);
+ } PA_RUNTIME_TEST_RUN_STOP
+ }
+
+ pa_memblock_unref(c0.memblock);
+ pa_memblock_unref(c1.memblock);
+
+ pa_mempool_unref(pool);
+}
+
+START_TEST (mix_special_test) {
+ pa_cpu_info cpu_info = { PA_CPU_UNDEFINED, {}, false };
+ pa_do_mix_func_t orig_func, special_func;
+
+ cpu_info.force_generic_code = true;
+ pa_mix_func_init(&cpu_info);
+ orig_func = pa_get_mix_func(PA_SAMPLE_S16NE);
+
+ cpu_info.force_generic_code = false;
+ pa_mix_func_init(&cpu_info);
+ special_func = pa_get_mix_func(PA_SAMPLE_S16NE);
+
+ pa_log_debug("Checking special mix (s16, stereo)");
+ run_mix_test(special_func, orig_func, 7, 2, true, true);
+
+ pa_log_debug("Checking special mix (s16, 4-channel)");
+ run_mix_test(special_func, orig_func, 7, 4, true, true);
+
+ pa_log_debug("Checking special mix (s16, mono)");
+ run_mix_test(special_func, orig_func, 7, 1, true, true);
+}
+END_TEST
+
+#if defined (__arm__) && defined (__linux__) && defined (HAVE_NEON)
+START_TEST (mix_neon_test) {
+ pa_do_mix_func_t orig_func, neon_func;
+ pa_cpu_arm_flag_t flags = 0;
+
+ pa_cpu_get_arm_flags(&flags);
+
+ if (!(flags & PA_CPU_ARM_NEON)) {
+ pa_log_info("NEON not supported. Skipping");
+ return;
+ }
+
+ orig_func = pa_get_mix_func(PA_SAMPLE_S16NE);
+ pa_mix_func_init_neon(flags);
+ neon_func = pa_get_mix_func(PA_SAMPLE_S16NE);
+
+ pa_log_debug("Checking NEON mix (s16, stereo)");
+ run_mix_test(neon_func, orig_func, 7, 2, true, true);
+
+ pa_log_debug("Checking NEON mix (s16, 4-channel)");
+ run_mix_test(neon_func, orig_func, 7, 4, true, true);
+
+ pa_log_debug("Checking NEON mix (s16, mono)");
+ run_mix_test(neon_func, orig_func, 7, 1, true, true);
+}
+END_TEST
+#endif /* defined (__arm__) && defined (__linux__) && defined (HAVE_NEON) */
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ if (!getenv("MAKE_CHECK"))
+ pa_log_set_level(PA_LOG_DEBUG);
+
+ s = suite_create("CPU");
+
+ tc = tcase_create("mix");
+ tcase_add_test(tc, mix_special_test);
+#if defined (__arm__) && defined (__linux__) && defined (HAVE_NEON)
+ tcase_add_test(tc, mix_neon_test);
+#endif
+ tcase_set_timeout(tc, 120);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/cpu-remap-test.c b/src/tests/cpu-remap-test.c
new file mode 100644
index 0000000..28c3b34
--- /dev/null
+++ b/src/tests/cpu-remap-test.c
@@ -0,0 +1,540 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <check.h>
+
+#include <pulsecore/cpu-x86.h>
+#include <pulsecore/cpu.h>
+#include <pulsecore/random.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/remap.h>
+#include <pulse/xmalloc.h>
+
+#include "runtime-test-util.h"
+
+#define SAMPLES 1027
+#define TIMES 1000
+#define TIMES2 100
+
+static void run_remap_test_float(
+ pa_remap_t *remap_func,
+ pa_remap_t *remap_orig,
+ int align,
+ bool correct,
+ bool perf) {
+
+ PA_DECLARE_ALIGNED(8, float, out_buf_ref[SAMPLES*8]) = { 0.0f, };
+ PA_DECLARE_ALIGNED(8, float, out_buf[SAMPLES*8]) = { 0.0f, };
+ PA_DECLARE_ALIGNED(8, float, in_buf[SAMPLES*8]);
+ float *out, *out_ref;
+ float *in;
+ unsigned n_ic = remap_func->i_ss.channels;
+ unsigned n_oc = remap_func->o_ss.channels;
+ unsigned i, nsamples;
+
+ pa_assert(n_ic >= 1 && n_ic <= 8);
+ pa_assert(n_oc >= 1 && n_oc <= 8);
+
+ /* Force sample alignment as requested */
+ out = out_buf + (8 - align);
+ out_ref = out_buf_ref + (8 - align);
+ in = in_buf + (8 - align);
+ nsamples = SAMPLES - (8 - align);
+
+ for (i = 0; i < nsamples * n_ic; i++)
+ in[i] = 2.1f * (rand()/(float) RAND_MAX - 0.5f);
+
+ if (correct) {
+ remap_orig->do_remap(remap_orig, out_ref, in, nsamples);
+ remap_func->do_remap(remap_func, out, in, nsamples);
+
+ for (i = 0; i < nsamples * n_oc; i++) {
+ if (fabsf(out[i] - out_ref[i]) > 0.0001f) {
+ pa_log_debug("Correctness test failed: align=%d", align);
+ pa_log_debug("%d: %.24f != %.24f", i,
+ out[i], out_ref[i]);
+ ck_abort();
+ }
+ }
+ }
+
+ if (perf) {
+ pa_log_debug("Testing remap performance with %d sample alignment", align);
+
+ PA_RUNTIME_TEST_RUN_START("func", TIMES, TIMES2) {
+ remap_func->do_remap(remap_func, out, in, nsamples);
+ } PA_RUNTIME_TEST_RUN_STOP
+
+ PA_RUNTIME_TEST_RUN_START("orig", TIMES, TIMES2) {
+ remap_orig->do_remap(remap_orig, out_ref, in, nsamples);
+ } PA_RUNTIME_TEST_RUN_STOP
+ }
+}
+
+static void run_remap_test_s16(
+ pa_remap_t *remap_func,
+ pa_remap_t *remap_orig,
+ int align,
+ bool correct,
+ bool perf) {
+
+ PA_DECLARE_ALIGNED(8, int16_t, out_buf_ref[SAMPLES*8]) = { 0 };
+ PA_DECLARE_ALIGNED(8, int16_t, out_buf[SAMPLES*8]) = { 0 };
+ PA_DECLARE_ALIGNED(8, int16_t, in_buf[SAMPLES*8]);
+ int16_t *out, *out_ref;
+ int16_t *in;
+ unsigned n_ic = remap_func->i_ss.channels;
+ unsigned n_oc = remap_func->o_ss.channels;
+ unsigned i, nsamples;
+
+ pa_assert(n_ic >= 1 && n_ic <= 8);
+ pa_assert(n_oc >= 1 && n_oc <= 8);
+
+ /* Force sample alignment as requested */
+ out = out_buf + (8 - align);
+ out_ref = out_buf_ref + (8 - align);
+ in = in_buf + (8 - align);
+ nsamples = SAMPLES - (8 - align);
+
+ pa_random(in, nsamples * n_ic * sizeof(int16_t));
+
+ if (correct) {
+ remap_orig->do_remap(remap_orig, out_ref, in, nsamples);
+ remap_func->do_remap(remap_func, out, in, nsamples);
+
+ for (i = 0; i < nsamples * n_oc; i++) {
+ if (abs(out[i] - out_ref[i]) > 3) {
+ pa_log_debug("Correctness test failed: align=%d", align);
+ pa_log_debug("%d: %d != %d", i, out[i], out_ref[i]);
+ ck_abort();
+ }
+ }
+ }
+
+ if (perf) {
+ pa_log_debug("Testing remap performance with %d sample alignment", align);
+
+ PA_RUNTIME_TEST_RUN_START("func", TIMES, TIMES2) {
+ remap_func->do_remap(remap_func, out, in, nsamples);
+ } PA_RUNTIME_TEST_RUN_STOP
+
+ PA_RUNTIME_TEST_RUN_START("orig", TIMES, TIMES2) {
+ remap_orig->do_remap(remap_orig, out_ref, in, nsamples);
+ } PA_RUNTIME_TEST_RUN_STOP
+ }
+}
+
+
+static void run_remap_test_s32(
+ pa_remap_t *remap_func,
+ pa_remap_t *remap_orig,
+ int align,
+ bool correct,
+ bool perf) {
+
+ PA_DECLARE_ALIGNED(8, int32_t, out_buf_ref[SAMPLES*8]) = { 0 };
+ PA_DECLARE_ALIGNED(8, int32_t, out_buf[SAMPLES*8]) = { 0 };
+ PA_DECLARE_ALIGNED(8, int32_t, in_buf[SAMPLES*8]);
+ int32_t *out, *out_ref;
+ int32_t *in;
+ unsigned n_ic = remap_func->i_ss.channels;
+ unsigned n_oc = remap_func->o_ss.channels;
+ unsigned i, nsamples;
+
+ pa_assert(n_ic >= 1 && n_ic <= 8);
+ pa_assert(n_oc >= 1 && n_oc <= 8);
+
+ /* Force sample alignment as requested */
+ out = out_buf + (8 - align);
+ out_ref = out_buf_ref + (8 - align);
+ in = in_buf + (8 - align);
+ nsamples = SAMPLES - (8 - align);
+
+ pa_random(in, nsamples * n_ic * sizeof(int32_t));
+
+ if (correct) {
+ remap_orig->do_remap(remap_orig, out_ref, in, nsamples);
+ remap_func->do_remap(remap_func, out, in, nsamples);
+
+ for (i = 0; i < nsamples * n_oc; i++) {
+ if (abs(out[i] - out_ref[i]) > 4) {
+ pa_log_debug("Correctness test failed: align=%d", align);
+ pa_log_debug("%d: %d != %d", i, out[i], out_ref[i]);
+ ck_abort();
+ }
+ }
+ }
+
+ if (perf) {
+ pa_log_debug("Testing remap performance with %d sample alignment", align);
+
+ PA_RUNTIME_TEST_RUN_START("func", TIMES, TIMES2) {
+ remap_func->do_remap(remap_func, out, in, nsamples);
+ } PA_RUNTIME_TEST_RUN_STOP
+
+ PA_RUNTIME_TEST_RUN_START("orig", TIMES, TIMES2) {
+ remap_orig->do_remap(remap_orig, out_ref, in, nsamples);
+ } PA_RUNTIME_TEST_RUN_STOP
+ }
+}
+
+static void setup_remap_channels(
+ pa_remap_t *m,
+ pa_sample_format_t f,
+ unsigned in_channels,
+ unsigned out_channels,
+ bool rearrange) {
+
+ unsigned i, o;
+
+ m->format = f;
+ m->i_ss.channels = in_channels;
+ m->o_ss.channels = out_channels;
+
+ if (rearrange) {
+ for (o = 0; o < out_channels; o++) {
+ for (i = 0; i < in_channels; i++) {
+ m->map_table_f[o][i] = (o == i) ? 1.0f : 0.0f;
+ m->map_table_i[o][i] = (o == i) ? 0x10000 : 0;
+ }
+ }
+ } else {
+ for (o = 0; o < out_channels; o++) {
+ for (i = 0; i < in_channels; i++) {
+ m->map_table_f[o][i] = 1.0f / in_channels;
+ m->map_table_i[o][i] = 0x10000 / in_channels;
+ }
+ }
+ }
+}
+
+static void remap_test_channels(
+ pa_remap_t *remap_func, pa_remap_t *remap_orig) {
+
+ if (!remap_orig->do_remap) {
+ pa_log_warn("No reference remapping function, abort test");
+ return;
+ }
+
+ if (!remap_func->do_remap || remap_func->do_remap == remap_orig->do_remap) {
+ pa_log_warn("No remapping function, abort test");
+ return;
+ }
+
+ pa_assert(remap_func->format == remap_orig->format);
+
+ switch (remap_func->format) {
+ case PA_SAMPLE_FLOAT32NE:
+ run_remap_test_float(remap_func, remap_orig, 0, true, false);
+ run_remap_test_float(remap_func, remap_orig, 1, true, false);
+ run_remap_test_float(remap_func, remap_orig, 2, true, false);
+ run_remap_test_float(remap_func, remap_orig, 3, true, true);
+ break;
+ case PA_SAMPLE_S32NE:
+ run_remap_test_s32(remap_func, remap_orig, 0, true, false);
+ run_remap_test_s32(remap_func, remap_orig, 1, true, false);
+ run_remap_test_s32(remap_func, remap_orig, 2, true, false);
+ run_remap_test_s32(remap_func, remap_orig, 3, true, true);
+ break;
+ case PA_SAMPLE_S16NE:
+ run_remap_test_s16(remap_func, remap_orig, 0, true, false);
+ run_remap_test_s16(remap_func, remap_orig, 1, true, false);
+ run_remap_test_s16(remap_func, remap_orig, 2, true, false);
+ run_remap_test_s16(remap_func, remap_orig, 3, true, true);
+ break;
+ default:
+ pa_assert_not_reached();
+ }
+}
+
+static void remap_init_test_channels(
+ pa_init_remap_func_t init_func,
+ pa_init_remap_func_t orig_init_func,
+ pa_sample_format_t f,
+ unsigned in_channels,
+ unsigned out_channels,
+ bool rearrange) {
+
+ pa_remap_t remap_orig = {0}, remap_func = {0};
+
+ setup_remap_channels(&remap_orig, f, in_channels, out_channels, rearrange);
+ orig_init_func(&remap_orig);
+
+ setup_remap_channels(&remap_func, f, in_channels, out_channels, rearrange);
+ init_func(&remap_func);
+
+ remap_test_channels(&remap_func, &remap_orig);
+}
+
+static void remap_init2_test_channels(
+ pa_sample_format_t f,
+ unsigned in_channels,
+ unsigned out_channels,
+ bool rearrange) {
+
+ pa_cpu_info cpu_info = { PA_CPU_UNDEFINED, {}, false };
+ pa_remap_t remap_orig, remap_func = {0};
+
+ cpu_info.force_generic_code = true;
+ pa_remap_func_init(&cpu_info);
+ setup_remap_channels(&remap_orig, f, in_channels, out_channels, rearrange);
+ pa_init_remap_func(&remap_orig);
+
+ cpu_info.force_generic_code = false;
+ pa_remap_func_init(&cpu_info);
+ setup_remap_channels(&remap_func, f, in_channels, out_channels, rearrange);
+ pa_init_remap_func(&remap_func);
+
+ remap_test_channels(&remap_func, &remap_orig);
+
+ pa_xfree(remap_func.state);
+}
+
+START_TEST (remap_special_test) {
+ pa_log_debug("Checking special remap (float, mono->stereo)");
+ remap_init2_test_channels(PA_SAMPLE_FLOAT32NE, 1, 2, false);
+ pa_log_debug("Checking special remap (float, mono->4-channel)");
+ remap_init2_test_channels(PA_SAMPLE_FLOAT32NE, 1, 4, false);
+
+ pa_log_debug("Checking special remap (s32, mono->stereo)");
+ remap_init2_test_channels(PA_SAMPLE_S32NE, 1, 2, false);
+ pa_log_debug("Checking special remap (s32, mono->4-channel)");
+ remap_init2_test_channels(PA_SAMPLE_S32NE, 1, 4, false);
+
+ pa_log_debug("Checking special remap (s16, mono->stereo)");
+ remap_init2_test_channels(PA_SAMPLE_S16NE, 1, 2, false);
+ pa_log_debug("Checking special remap (s16, mono->4-channel)");
+ remap_init2_test_channels(PA_SAMPLE_S16NE, 1, 4, false);
+
+ pa_log_debug("Checking special remap (float, stereo->mono)");
+ remap_init2_test_channels(PA_SAMPLE_FLOAT32NE, 2, 1, false);
+ pa_log_debug("Checking special remap (float, 4-channel->mono)");
+ remap_init2_test_channels(PA_SAMPLE_FLOAT32NE, 4, 1, false);
+
+ pa_log_debug("Checking special remap (s32, stereo->mono)");
+ remap_init2_test_channels(PA_SAMPLE_S32NE, 2, 1, false);
+ pa_log_debug("Checking special remap (s32, 4-channel->mono)");
+ remap_init2_test_channels(PA_SAMPLE_S32NE, 4, 1, false);
+
+ pa_log_debug("Checking special remap (s16, stereo->mono)");
+ remap_init2_test_channels(PA_SAMPLE_S16NE, 2, 1, false);
+ pa_log_debug("Checking special remap (s16, 4-channel->mono)");
+ remap_init2_test_channels(PA_SAMPLE_S16NE, 4, 1, false);
+}
+END_TEST
+
+START_TEST (rearrange_special_test) {
+ pa_log_debug("Checking special remap (s16, stereo rearrange)");
+ remap_init2_test_channels(PA_SAMPLE_S16NE, 2, 2, true);
+ pa_log_debug("Checking special remap (s32, stereo rearrange)");
+ remap_init2_test_channels(PA_SAMPLE_S32NE, 2, 2, true);
+ pa_log_debug("Checking special remap (float, stereo rearrange)");
+ remap_init2_test_channels(PA_SAMPLE_FLOAT32NE, 2, 2, true);
+
+ pa_log_debug("Checking special remap (s16, 4-channel rearrange)");
+ remap_init2_test_channels(PA_SAMPLE_S16NE, 4, 4, true);
+ pa_log_debug("Checking special remap (s32, 4-channel rearrange)");
+ remap_init2_test_channels(PA_SAMPLE_S32NE, 4, 4, true);
+ pa_log_debug("Checking special remap (float, 4-channel rearrange)");
+ remap_init2_test_channels(PA_SAMPLE_FLOAT32NE, 4, 4, true);
+}
+END_TEST
+
+#if defined (__i386__) || defined (__amd64__)
+START_TEST (remap_mmx_test) {
+ pa_cpu_x86_flag_t flags = 0;
+ pa_init_remap_func_t init_func, orig_init_func;
+
+ pa_cpu_get_x86_flags(&flags);
+ if (!(flags & PA_CPU_X86_MMX)) {
+ pa_log_info("MMX not supported. Skipping");
+ return;
+ }
+
+ pa_log_debug("Checking MMX remap (float, mono->stereo)");
+ orig_init_func = pa_get_init_remap_func();
+ pa_remap_func_init_mmx(flags);
+ init_func = pa_get_init_remap_func();
+ remap_init_test_channels(init_func, orig_init_func, PA_SAMPLE_FLOAT32NE, 1, 2, false);
+
+ pa_log_debug("Checking MMX remap (s32, mono->stereo)");
+ remap_init_test_channels(init_func, orig_init_func, PA_SAMPLE_S32NE, 1, 2, false);
+
+ pa_log_debug("Checking MMX remap (s16, mono->stereo)");
+ remap_init_test_channels(init_func, orig_init_func, PA_SAMPLE_S16NE, 1, 2, false);
+}
+END_TEST
+
+START_TEST (remap_sse2_test) {
+ pa_cpu_x86_flag_t flags = 0;
+ pa_init_remap_func_t init_func, orig_init_func;
+
+ pa_cpu_get_x86_flags(&flags);
+ if (!(flags & PA_CPU_X86_SSE2)) {
+ pa_log_info("SSE2 not supported. Skipping");
+ return;
+ }
+
+ pa_log_debug("Checking SSE2 remap (float, mono->stereo)");
+ orig_init_func = pa_get_init_remap_func();
+ pa_remap_func_init_sse(flags);
+ init_func = pa_get_init_remap_func();
+ remap_init_test_channels(init_func, orig_init_func, PA_SAMPLE_FLOAT32NE, 1, 2, false);
+
+ pa_log_debug("Checking SSE2 remap (s32, mono->stereo)");
+ remap_init_test_channels(init_func, orig_init_func, PA_SAMPLE_S32NE, 1, 2, false);
+
+ pa_log_debug("Checking SSE2 remap (s16, mono->stereo)");
+ remap_init_test_channels(init_func, orig_init_func, PA_SAMPLE_S16NE, 1, 2, false);
+}
+END_TEST
+#endif /* defined (__i386__) || defined (__amd64__) */
+
+#if defined (__arm__) && defined (__linux__) && defined (HAVE_NEON)
+START_TEST (remap_neon_test) {
+ pa_cpu_arm_flag_t flags = 0;
+ pa_init_remap_func_t init_func, orig_init_func;
+
+ pa_cpu_get_arm_flags(&flags);
+ if (!(flags & PA_CPU_ARM_NEON)) {
+ pa_log_info("NEON not supported. Skipping");
+ return;
+ }
+
+ orig_init_func = pa_get_init_remap_func();
+ pa_remap_func_init_neon(flags);
+ init_func = pa_get_init_remap_func();
+
+ pa_log_debug("Checking NEON remap (float, mono->stereo)");
+ remap_init_test_channels(init_func, orig_init_func, PA_SAMPLE_FLOAT32NE, 1, 2, false);
+ pa_log_debug("Checking NEON remap (float, mono->4-channel)");
+ remap_init_test_channels(init_func, orig_init_func, PA_SAMPLE_FLOAT32NE, 1, 4, false);
+
+ pa_log_debug("Checking NEON remap (s32, mono->stereo)");
+ remap_init_test_channels(init_func, orig_init_func, PA_SAMPLE_S32NE, 1, 2, false);
+ pa_log_debug("Checking NEON remap (s32, mono->4-channel)");
+ remap_init_test_channels(init_func, orig_init_func, PA_SAMPLE_S32NE, 1, 4, false);
+
+ pa_log_debug("Checking NEON remap (s16, mono->stereo)");
+ remap_init_test_channels(init_func, orig_init_func, PA_SAMPLE_S16NE, 1, 2, false);
+ pa_log_debug("Checking NEON remap (s16, mono->4-channel)");
+ remap_init_test_channels(init_func, orig_init_func, PA_SAMPLE_S16NE, 1, 4, false);
+
+ pa_log_debug("Checking NEON remap (float, stereo->mono)");
+ remap_init_test_channels(init_func, orig_init_func, PA_SAMPLE_FLOAT32NE, 2, 1, false);
+ pa_log_debug("Checking NEON remap (float, 4-channel->mono)");
+ remap_init_test_channels(init_func, orig_init_func, PA_SAMPLE_FLOAT32NE, 4, 1, false);
+
+ pa_log_debug("Checking NEON remap (s32, stereo->mono)");
+ remap_init_test_channels(init_func, orig_init_func, PA_SAMPLE_S32NE, 2, 1, false);
+ pa_log_debug("Checking NEON remap (s32, 4-channel->mono)");
+ remap_init_test_channels(init_func, orig_init_func, PA_SAMPLE_S32NE, 4, 1, false);
+
+ pa_log_debug("Checking NEON remap (s16, stereo->mono)");
+ remap_init_test_channels(init_func, orig_init_func, PA_SAMPLE_S16NE, 2, 1, false);
+ pa_log_debug("Checking NEON remap (s16, 4-channel->mono)");
+ remap_init_test_channels(init_func, orig_init_func, PA_SAMPLE_S16NE, 4, 1, false);
+
+ pa_log_debug("Checking NEON remap (float, 4-channel->4-channel)");
+ remap_init_test_channels(init_func, orig_init_func, PA_SAMPLE_FLOAT32NE, 4, 4, false);
+ pa_log_debug("Checking NEON remap (s32, 4-channel->4-channel)");
+ remap_init_test_channels(init_func, orig_init_func, PA_SAMPLE_S32NE, 4, 4, false);
+ pa_log_debug("Checking NEON remap (s16, 4-channel->4-channel)");
+ remap_init_test_channels(init_func, orig_init_func, PA_SAMPLE_S16NE, 4, 4, false);
+}
+END_TEST
+
+START_TEST (rearrange_neon_test) {
+ pa_cpu_arm_flag_t flags = 0;
+ pa_init_remap_func_t init_func, orig_init_func;
+
+ pa_cpu_get_arm_flags(&flags);
+ if (!(flags & PA_CPU_ARM_NEON)) {
+ pa_log_info("NEON not supported. Skipping");
+ return;
+ }
+
+ orig_init_func = pa_get_init_remap_func();
+ pa_remap_func_init_neon(flags);
+ init_func = pa_get_init_remap_func();
+
+ pa_log_debug("Checking NEON remap (float, stereo rearrange)");
+ remap_init_test_channels(init_func, orig_init_func, PA_SAMPLE_FLOAT32NE, 2, 2, true);
+ pa_log_debug("Checking NEON remap (s32, stereo rearrange)");
+ remap_init_test_channels(init_func, orig_init_func, PA_SAMPLE_S32NE, 2, 2, true);
+ pa_log_debug("Checking NEON remap (s16, stereo rearrange)");
+ remap_init_test_channels(init_func, orig_init_func, PA_SAMPLE_S16NE, 2, 2, true);
+
+ pa_log_debug("Checking NEON remap (float, 2-channel->4-channel rearrange)");
+ remap_init_test_channels(init_func, orig_init_func, PA_SAMPLE_FLOAT32NE, 2, 4, true);
+ pa_log_debug("Checking NEON remap (s32, 2-channel->4-channel rearrange)");
+ remap_init_test_channels(init_func, orig_init_func, PA_SAMPLE_S32NE, 2, 4, true);
+ pa_log_debug("Checking NEON remap (s16, 2-channel->4-channel rearrange)");
+ remap_init_test_channels(init_func, orig_init_func, PA_SAMPLE_S16NE, 2, 4, true);
+
+ pa_log_debug("Checking NEON remap (float, 4-channel rearrange)");
+ remap_init_test_channels(init_func, orig_init_func, PA_SAMPLE_FLOAT32NE, 4, 4, true);
+ pa_log_debug("Checking NEON remap (s32, 4-channel rearrange)");
+ remap_init_test_channels(init_func, orig_init_func, PA_SAMPLE_S32NE, 4, 4, true);
+ pa_log_debug("Checking NEON remap (s16, 4-channel rearrange)");
+ remap_init_test_channels(init_func, orig_init_func, PA_SAMPLE_S16NE, 4, 4, true);
+}
+END_TEST
+#endif
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ if (!getenv("MAKE_CHECK"))
+ pa_log_set_level(PA_LOG_DEBUG);
+
+ s = suite_create("CPU");
+
+ tc = tcase_create("remap");
+ tcase_add_test(tc, remap_special_test);
+#if defined (__i386__) || defined (__amd64__)
+ tcase_add_test(tc, remap_mmx_test);
+ tcase_add_test(tc, remap_sse2_test);
+#endif
+#if defined (__arm__) && defined (__linux__) && defined (HAVE_NEON)
+ tcase_add_test(tc, remap_neon_test);
+#endif
+ tcase_set_timeout(tc, 120);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("rearrange");
+ tcase_add_test(tc, rearrange_special_test);
+#if defined (__arm__) && defined (__linux__) && defined (HAVE_NEON)
+ tcase_add_test(tc, rearrange_neon_test);
+#endif
+ tcase_set_timeout(tc, 120);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/cpu-sconv-test.c b/src/tests/cpu-sconv-test.c
new file mode 100644
index 0000000..3f189d1
--- /dev/null
+++ b/src/tests/cpu-sconv-test.c
@@ -0,0 +1,263 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <check.h>
+
+#include <pulsecore/cpu-arm.h>
+#include <pulsecore/cpu-x86.h>
+#include <pulsecore/random.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/sconv.h>
+
+#include "runtime-test-util.h"
+
+#define SAMPLES 1028
+#define TIMES 1000
+#define TIMES2 100
+
+static void run_conv_test_float_to_s16(
+ pa_convert_func_t func,
+ pa_convert_func_t orig_func,
+ int align,
+ bool correct,
+ bool perf) {
+
+ PA_DECLARE_ALIGNED(8, int16_t, s[SAMPLES]) = { 0 };
+ PA_DECLARE_ALIGNED(8, int16_t, s_ref[SAMPLES]) = { 0 };
+ PA_DECLARE_ALIGNED(8, float, f[SAMPLES]);
+ int16_t *samples, *samples_ref;
+ float *floats;
+ int i, nsamples;
+
+ /* Force sample alignment as requested */
+ samples = s + (8 - align);
+ samples_ref = s_ref + (8 - align);
+ floats = f + (8 - align);
+ nsamples = SAMPLES - (8 - align);
+
+ for (i = 0; i < nsamples; i++) {
+ floats[i] = 2.1f * (rand()/(float) RAND_MAX - 0.5f);
+ }
+
+ if (correct) {
+ orig_func(nsamples, floats, samples_ref);
+ func(nsamples, floats, samples);
+
+ for (i = 0; i < nsamples; i++) {
+ if (abs(samples[i] - samples_ref[i]) > 1) {
+ pa_log_debug("Correctness test failed: align=%d", align);
+ pa_log_debug("%d: %04hx != %04hx (%.24f)\n", i, samples[i], samples_ref[i], floats[i]);
+ ck_abort();
+ }
+ }
+ }
+
+ if (perf) {
+ pa_log_debug("Testing sconv performance with %d sample alignment", align);
+
+ PA_RUNTIME_TEST_RUN_START("func", TIMES, TIMES2) {
+ func(nsamples, floats, samples);
+ } PA_RUNTIME_TEST_RUN_STOP
+
+ PA_RUNTIME_TEST_RUN_START("orig", TIMES, TIMES2) {
+ orig_func(nsamples, floats, samples_ref);
+ } PA_RUNTIME_TEST_RUN_STOP
+ }
+}
+
+/* This test is currently only run under NEON */
+#if defined (__arm__) && defined (__linux__) && defined (HAVE_NEON)
+static void run_conv_test_s16_to_float(
+ pa_convert_func_t func,
+ pa_convert_func_t orig_func,
+ int align,
+ bool correct,
+ bool perf) {
+
+ PA_DECLARE_ALIGNED(8, float, f[SAMPLES]) = { 0.0f };
+ PA_DECLARE_ALIGNED(8, float, f_ref[SAMPLES]) = { 0.0f };
+ PA_DECLARE_ALIGNED(8, int16_t, s[SAMPLES]);
+ float *floats, *floats_ref;
+ int16_t *samples;
+ int i, nsamples;
+
+ /* Force sample alignment as requested */
+ floats = f + (8 - align);
+ floats_ref = f_ref + (8 - align);
+ samples = s + (8 - align);
+ nsamples = SAMPLES - (8 - align);
+
+ pa_random(samples, nsamples * sizeof(int16_t));
+
+ if (correct) {
+ orig_func(nsamples, samples, floats_ref);
+ func(nsamples, samples, floats);
+
+ for (i = 0; i < nsamples; i++) {
+ if (fabsf(floats[i] - floats_ref[i]) > 0.0001f) {
+ pa_log_debug("Correctness test failed: align=%d", align);
+ pa_log_debug("%d: %.24f != %.24f (%d)\n", i, floats[i], floats_ref[i], samples[i]);
+ ck_abort();
+ }
+ }
+ }
+
+ if (perf) {
+ pa_log_debug("Testing sconv performance with %d sample alignment", align);
+
+ PA_RUNTIME_TEST_RUN_START("func", TIMES, TIMES2) {
+ func(nsamples, samples, floats);
+ } PA_RUNTIME_TEST_RUN_STOP
+
+ PA_RUNTIME_TEST_RUN_START("orig", TIMES, TIMES2) {
+ orig_func(nsamples, samples, floats_ref);
+ } PA_RUNTIME_TEST_RUN_STOP
+ }
+}
+#endif /* defined (__arm__) && defined (__linux__) && defined (HAVE_NEON) */
+
+#if defined (__i386__) || defined (__amd64__)
+START_TEST (sconv_sse2_test) {
+ pa_cpu_x86_flag_t flags = 0;
+ pa_convert_func_t orig_func, sse2_func;
+
+ pa_cpu_get_x86_flags(&flags);
+
+ if (!(flags & PA_CPU_X86_SSE2)) {
+ pa_log_info("SSE2 not supported. Skipping");
+ return;
+ }
+
+ orig_func = pa_get_convert_from_float32ne_function(PA_SAMPLE_S16LE);
+ pa_convert_func_init_sse(PA_CPU_X86_SSE2);
+ sse2_func = pa_get_convert_from_float32ne_function(PA_SAMPLE_S16LE);
+
+ pa_log_debug("Checking SSE2 sconv (float -> s16)");
+ run_conv_test_float_to_s16(sse2_func, orig_func, 0, true, false);
+ run_conv_test_float_to_s16(sse2_func, orig_func, 1, true, false);
+ run_conv_test_float_to_s16(sse2_func, orig_func, 2, true, false);
+ run_conv_test_float_to_s16(sse2_func, orig_func, 3, true, false);
+ run_conv_test_float_to_s16(sse2_func, orig_func, 4, true, false);
+ run_conv_test_float_to_s16(sse2_func, orig_func, 5, true, false);
+ run_conv_test_float_to_s16(sse2_func, orig_func, 6, true, false);
+ run_conv_test_float_to_s16(sse2_func, orig_func, 7, true, true);
+}
+END_TEST
+
+START_TEST (sconv_sse_test) {
+ pa_cpu_x86_flag_t flags = 0;
+ pa_convert_func_t orig_func, sse_func;
+
+ pa_cpu_get_x86_flags(&flags);
+
+ if (!(flags & PA_CPU_X86_SSE)) {
+ pa_log_info("SSE not supported. Skipping");
+ return;
+ }
+
+ orig_func = pa_get_convert_from_float32ne_function(PA_SAMPLE_S16LE);
+ pa_convert_func_init_sse(PA_CPU_X86_SSE);
+ sse_func = pa_get_convert_from_float32ne_function(PA_SAMPLE_S16LE);
+
+ pa_log_debug("Checking SSE sconv (float -> s16)");
+ run_conv_test_float_to_s16(sse_func, orig_func, 0, true, false);
+ run_conv_test_float_to_s16(sse_func, orig_func, 1, true, false);
+ run_conv_test_float_to_s16(sse_func, orig_func, 2, true, false);
+ run_conv_test_float_to_s16(sse_func, orig_func, 3, true, false);
+ run_conv_test_float_to_s16(sse_func, orig_func, 4, true, false);
+ run_conv_test_float_to_s16(sse_func, orig_func, 5, true, false);
+ run_conv_test_float_to_s16(sse_func, orig_func, 6, true, false);
+ run_conv_test_float_to_s16(sse_func, orig_func, 7, true, true);
+}
+END_TEST
+#endif /* defined (__i386__) || defined (__amd64__) */
+
+#if defined (__arm__) && defined (__linux__) && defined (HAVE_NEON)
+START_TEST (sconv_neon_test) {
+ pa_cpu_arm_flag_t flags = 0;
+ pa_convert_func_t orig_from_func, neon_from_func;
+ pa_convert_func_t orig_to_func, neon_to_func;
+
+ pa_cpu_get_arm_flags(&flags);
+
+ if (!(flags & PA_CPU_ARM_NEON)) {
+ pa_log_info("NEON not supported. Skipping");
+ return;
+ }
+
+ orig_from_func = pa_get_convert_from_float32ne_function(PA_SAMPLE_S16LE);
+ orig_to_func = pa_get_convert_to_float32ne_function(PA_SAMPLE_S16LE);
+ pa_convert_func_init_neon(flags);
+ neon_from_func = pa_get_convert_from_float32ne_function(PA_SAMPLE_S16LE);
+ neon_to_func = pa_get_convert_to_float32ne_function(PA_SAMPLE_S16LE);
+
+ pa_log_debug("Checking NEON sconv (float -> s16)");
+ run_conv_test_float_to_s16(neon_from_func, orig_from_func, 0, true, false);
+ run_conv_test_float_to_s16(neon_from_func, orig_from_func, 1, true, false);
+ run_conv_test_float_to_s16(neon_from_func, orig_from_func, 2, true, false);
+ run_conv_test_float_to_s16(neon_from_func, orig_from_func, 3, true, false);
+ run_conv_test_float_to_s16(neon_from_func, orig_from_func, 4, true, false);
+ run_conv_test_float_to_s16(neon_from_func, orig_from_func, 5, true, false);
+ run_conv_test_float_to_s16(neon_from_func, orig_from_func, 6, true, false);
+ run_conv_test_float_to_s16(neon_from_func, orig_from_func, 7, true, true);
+
+ pa_log_debug("Checking NEON sconv (s16 -> float)");
+ run_conv_test_s16_to_float(neon_to_func, orig_to_func, 0, true, false);
+ run_conv_test_s16_to_float(neon_to_func, orig_to_func, 1, true, false);
+ run_conv_test_s16_to_float(neon_to_func, orig_to_func, 2, true, false);
+ run_conv_test_s16_to_float(neon_to_func, orig_to_func, 3, true, false);
+ run_conv_test_s16_to_float(neon_to_func, orig_to_func, 4, true, false);
+ run_conv_test_s16_to_float(neon_to_func, orig_to_func, 5, true, false);
+ run_conv_test_s16_to_float(neon_to_func, orig_to_func, 6, true, false);
+ run_conv_test_s16_to_float(neon_to_func, orig_to_func, 7, true, true);
+}
+END_TEST
+#endif /* defined (__arm__) && defined (__linux__) && defined (HAVE_NEON) */
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ if (!getenv("MAKE_CHECK"))
+ pa_log_set_level(PA_LOG_DEBUG);
+
+ s = suite_create("CPU");
+
+ tc = tcase_create("sconv");
+#if defined (__i386__) || defined (__amd64__)
+ tcase_add_test(tc, sconv_sse2_test);
+ tcase_add_test(tc, sconv_sse_test);
+#endif
+#if defined (__arm__) && defined (__linux__) && defined (HAVE_NEON)
+ tcase_add_test(tc, sconv_neon_test);
+#endif
+ tcase_set_timeout(tc, 120);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/cpu-volume-test.c b/src/tests/cpu-volume-test.c
new file mode 100644
index 0000000..535488e
--- /dev/null
+++ b/src/tests/cpu-volume-test.c
@@ -0,0 +1,247 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <check.h>
+
+#include <pulsecore/cpu-arm.h>
+#include <pulsecore/cpu-x86.h>
+#include <pulsecore/cpu-orc.h>
+#include <pulsecore/random.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/sample-util.h>
+
+#include "runtime-test-util.h"
+
+/* Common defines for svolume tests */
+#define SAMPLES 1028
+#define TIMES 1000
+#define TIMES2 100
+#define PADDING 16
+
+static void run_volume_test(
+ pa_do_volume_func_t func,
+ pa_do_volume_func_t orig_func,
+ int align,
+ int channels,
+ bool correct,
+ bool perf) {
+
+ PA_DECLARE_ALIGNED(8, int16_t, s[SAMPLES]) = { 0 };
+ PA_DECLARE_ALIGNED(8, int16_t, s_ref[SAMPLES]) = { 0 };
+ PA_DECLARE_ALIGNED(8, int16_t, s_orig[SAMPLES]) = { 0 };
+ int32_t volumes[channels + PADDING];
+ int16_t *samples, *samples_ref, *samples_orig;
+ int i, padding, nsamples, size;
+
+ /* Force sample alignment as requested */
+ samples = s + (8 - align);
+ samples_ref = s_ref + (8 - align);
+ samples_orig = s_orig + (8 - align);
+ nsamples = SAMPLES - (8 - align);
+ if (nsamples % channels)
+ nsamples -= nsamples % channels;
+ size = nsamples * sizeof(int16_t);
+
+ pa_random(samples, size);
+ memcpy(samples_ref, samples, size);
+ memcpy(samples_orig, samples, size);
+
+ for (i = 0; i < channels; i++)
+ volumes[i] = PA_CLAMP_VOLUME((pa_volume_t)(rand() >> 15));
+ for (padding = 0; padding < PADDING; padding++, i++)
+ volumes[i] = volumes[padding];
+
+ if (correct) {
+ orig_func(samples_ref, volumes, channels, size);
+ func(samples, volumes, channels, size);
+
+ for (i = 0; i < nsamples; i++) {
+ if (samples[i] != samples_ref[i]) {
+ pa_log_debug("Correctness test failed: align=%d, channels=%d", align, channels);
+ pa_log_debug("%d: %04hx != %04hx (%04hx * %08x)", i, samples[i], samples_ref[i],
+ samples_orig[i], volumes[i % channels]);
+ ck_abort();
+ }
+ }
+ }
+
+ if (perf) {
+ pa_log_debug("Testing svolume %dch performance with %d sample alignment", channels, align);
+
+ PA_RUNTIME_TEST_RUN_START("func", TIMES, TIMES2) {
+ memcpy(samples, samples_orig, size);
+ func(samples, volumes, channels, size);
+ } PA_RUNTIME_TEST_RUN_STOP
+
+ PA_RUNTIME_TEST_RUN_START("orig", TIMES, TIMES2) {
+ memcpy(samples_ref, samples_orig, size);
+ orig_func(samples_ref, volumes, channels, size);
+ } PA_RUNTIME_TEST_RUN_STOP
+
+ fail_unless(memcmp(samples_ref, samples, size) == 0);
+ }
+}
+
+#if defined (__i386__) || defined (__amd64__)
+START_TEST (svolume_mmx_test) {
+ pa_do_volume_func_t orig_func, mmx_func;
+ pa_cpu_x86_flag_t flags = 0;
+ int i, j;
+
+ pa_cpu_get_x86_flags(&flags);
+
+ if (!((flags & PA_CPU_X86_MMX) && (flags & PA_CPU_X86_CMOV))) {
+ pa_log_info("MMX/CMOV not supported. Skipping");
+ return;
+ }
+
+ orig_func = pa_get_volume_func(PA_SAMPLE_S16NE);
+ pa_volume_func_init_mmx(flags);
+ mmx_func = pa_get_volume_func(PA_SAMPLE_S16NE);
+
+ pa_log_debug("Checking MMX svolume");
+ for (i = 1; i <= 3; i++) {
+ for (j = 0; j < 7; j++)
+ run_volume_test(mmx_func, orig_func, j, i, true, false);
+ }
+ run_volume_test(mmx_func, orig_func, 7, 1, true, true);
+ run_volume_test(mmx_func, orig_func, 7, 2, true, true);
+ run_volume_test(mmx_func, orig_func, 7, 3, true, true);
+}
+END_TEST
+
+START_TEST (svolume_sse_test) {
+ pa_do_volume_func_t orig_func, sse_func;
+ pa_cpu_x86_flag_t flags = 0;
+ int i, j;
+
+ pa_cpu_get_x86_flags(&flags);
+
+ if (!(flags & PA_CPU_X86_SSE2)) {
+ pa_log_info("SSE2 not supported. Skipping");
+ return;
+ }
+
+ orig_func = pa_get_volume_func(PA_SAMPLE_S16NE);
+ pa_volume_func_init_sse(flags);
+ sse_func = pa_get_volume_func(PA_SAMPLE_S16NE);
+
+ pa_log_debug("Checking SSE2 svolume");
+ for (i = 1; i <= 3; i++) {
+ for (j = 0; j < 7; j++)
+ run_volume_test(sse_func, orig_func, j, i, true, false);
+ }
+ run_volume_test(sse_func, orig_func, 7, 1, true, true);
+ run_volume_test(sse_func, orig_func, 7, 2, true, true);
+ run_volume_test(sse_func, orig_func, 7, 3, true, true);
+}
+END_TEST
+#endif /* defined (__i386__) || defined (__amd64__) */
+
+#if defined (__arm__) && defined (__linux__)
+START_TEST (svolume_arm_test) {
+ pa_do_volume_func_t orig_func, arm_func;
+ pa_cpu_arm_flag_t flags = 0;
+ int i, j;
+
+ pa_cpu_get_arm_flags(&flags);
+
+ if (!(flags & PA_CPU_ARM_V6)) {
+ pa_log_info("ARMv6 instructions not supported. Skipping");
+ return;
+ }
+
+ orig_func = pa_get_volume_func(PA_SAMPLE_S16NE);
+ pa_volume_func_init_arm(flags);
+ arm_func = pa_get_volume_func(PA_SAMPLE_S16NE);
+
+ pa_log_debug("Checking ARM svolume");
+ for (i = 1; i <= 3; i++) {
+ for (j = 0; j < 7; j++)
+ run_volume_test(arm_func, orig_func, j, i, true, false);
+ }
+ run_volume_test(arm_func, orig_func, 7, 1, true, true);
+ run_volume_test(arm_func, orig_func, 7, 2, true, true);
+ run_volume_test(arm_func, orig_func, 7, 3, true, true);
+}
+END_TEST
+#endif /* defined (__arm__) && defined (__linux__) */
+
+START_TEST (svolume_orc_test) {
+ pa_do_volume_func_t orig_func, orc_func;
+ pa_cpu_info cpu_info;
+ int i, j;
+
+#if defined (__i386__) || defined (__amd64__)
+ pa_zero(cpu_info);
+ cpu_info.cpu_type = PA_CPU_X86;
+ pa_cpu_get_x86_flags(&cpu_info.flags.x86);
+#endif
+
+ orig_func = pa_get_volume_func(PA_SAMPLE_S16NE);
+
+ if (!pa_cpu_init_orc(cpu_info)) {
+ pa_log_info("Orc not supported. Skipping");
+ return;
+ }
+
+ orc_func = pa_get_volume_func(PA_SAMPLE_S16NE);
+
+ pa_log_debug("Checking Orc svolume");
+ for (i = 1; i <= 2; i++) {
+ for (j = 0; j < 7; j++)
+ run_volume_test(orc_func, orig_func, j, i, true, false);
+ }
+ run_volume_test(orc_func, orig_func, 7, 1, true, true);
+ run_volume_test(orc_func, orig_func, 7, 2, true, true);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ if (!getenv("MAKE_CHECK"))
+ pa_log_set_level(PA_LOG_DEBUG);
+
+ s = suite_create("CPU");
+
+ tc = tcase_create("svolume");
+#if defined (__i386__) || defined (__amd64__)
+ tcase_add_test(tc, svolume_mmx_test);
+ tcase_add_test(tc, svolume_sse_test);
+#endif
+#if defined (__arm__) && defined (__linux__)
+ tcase_add_test(tc, svolume_arm_test);
+#endif
+ tcase_add_test(tc, svolume_orc_test);
+ tcase_set_timeout(tc, 120);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/cpulimit-test.c b/src/tests/cpulimit-test.c
new file mode 100644
index 0000000..e01a5b8
--- /dev/null
+++ b/src/tests/cpulimit-test.c
@@ -0,0 +1,110 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <time.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <signal.h>
+
+#include <check.h>
+
+#include <pulse/mainloop.h>
+
+#ifdef TEST2
+#include <pulse/mainloop-signal.h>
+#endif
+
+#include <daemon/cpulimit.h>
+
+/* A simple example for testing the cpulimit subsystem */
+
+static time_t start;
+
+#ifdef TEST2
+
+static void func(pa_mainloop_api *m, pa_signal_event *e, int sig, void *userdata) {
+ time_t now;
+ time(&now);
+
+ if ((now - start) >= 30) {
+ m->quit(m, 1);
+ fprintf(stderr, "Test failed\n");
+ ck_abort();
+ } else
+ raise(SIGUSR1);
+}
+
+#endif
+
+START_TEST (cpulimit_test) {
+ pa_mainloop *m;
+
+ m = pa_mainloop_new();
+ fail_unless(m != NULL);
+
+ pa_cpu_limit_init(pa_mainloop_get_api(m));
+
+ time(&start);
+
+#ifdef TEST2
+ pa_signal_init(pa_mainloop_get_api(m));
+ pa_signal_new(SIGUSR1, func, NULL);
+ raise(SIGUSR1);
+ pa_mainloop_run(m, NULL);
+ pa_signal_done();
+#else
+ for (;;) {
+ time_t now;
+ time(&now);
+
+ if ((now - start) >= 30) {
+ fprintf(stderr, "Test failed\n");
+ ck_abort();
+ break;
+ }
+ }
+#endif
+
+ pa_cpu_limit_done();
+
+ pa_mainloop_free(m);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ s = suite_create("CPU Limit");
+ tc = tcase_create("cpulimit");
+ tcase_add_test(tc, cpulimit_test);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/extended-test.c b/src/tests/extended-test.c
new file mode 100644
index 0000000..0d08fac
--- /dev/null
+++ b/src/tests/extended-test.c
@@ -0,0 +1,222 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <signal.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include <check.h>
+
+#include <pulse/pulseaudio.h>
+#include <pulse/mainloop.h>
+
+#define NSTREAMS 4
+#define SINE_HZ 440
+#define SAMPLE_HZ 8000
+
+static pa_context *context = NULL;
+static pa_stream *streams[NSTREAMS];
+static pa_mainloop_api *mainloop_api = NULL;
+static const char *bname;
+
+static float data[SAMPLE_HZ]; /* one second space */
+
+static int n_streams_ready = 0;
+
+static const pa_buffer_attr buffer_attr = {
+ .maxlength = SAMPLE_HZ*sizeof(float)*NSTREAMS, /* exactly space for the entire play time */
+ .tlength = (uint32_t) -1,
+ .prebuf = 0, /* Setting prebuf to 0 guarantees us the streams will run synchronously, no matter what */
+ .minreq = (uint32_t) -1,
+ .fragsize = 0
+};
+
+static void nop_free_cb(void *p) {}
+
+static void underflow_cb(struct pa_stream *s, void *userdata) {
+ int i = (int) (long) userdata;
+
+ fprintf(stderr, "Stream %i finished\n", i);
+
+ if (++n_streams_ready >= 2*NSTREAMS) {
+ fprintf(stderr, "We're done\n");
+ mainloop_api->quit(mainloop_api, 0);
+ }
+}
+
+/* This routine is called whenever the stream state changes */
+static void stream_state_callback(pa_stream *s, void *userdata) {
+ fail_unless(s != NULL);
+
+ switch (pa_stream_get_state(s)) {
+ case PA_STREAM_UNCONNECTED:
+ case PA_STREAM_CREATING:
+ case PA_STREAM_TERMINATED:
+ break;
+
+ case PA_STREAM_READY: {
+
+ int r, i = (int) (long) userdata;
+
+ fprintf(stderr, "Writing data to stream %i.\n", i);
+
+ r = pa_stream_write(s, data, sizeof(data), nop_free_cb, (int64_t) sizeof(data) * (int64_t) i, PA_SEEK_ABSOLUTE);
+ fail_unless(r == 0);
+
+ /* Be notified when this stream is drained */
+ pa_stream_set_underflow_callback(s, underflow_cb, userdata);
+
+ /* All streams have been set up, let's go! */
+ if (++n_streams_ready >= NSTREAMS) {
+ fprintf(stderr, "Uncorking\n");
+ pa_operation_unref(pa_stream_cork(s, 0, NULL, NULL));
+ }
+
+ break;
+ }
+
+ default:
+ case PA_STREAM_FAILED:
+ fprintf(stderr, "Stream error: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
+ ck_abort();
+ }
+}
+
+/* This is called whenever the context status changes */
+static void context_state_callback(pa_context *c, void *userdata) {
+ fail_unless(c != NULL);
+
+ switch (pa_context_get_state(c)) {
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+
+ case PA_CONTEXT_READY: {
+
+ int i;
+ fprintf(stderr, "Connection established.\n");
+
+ for (i = 0; i < NSTREAMS; i++) {
+ char name[64];
+ pa_format_info *formats[1];
+
+ formats[0] = pa_format_info_new();
+ formats[0]->encoding = PA_ENCODING_PCM;
+ pa_format_info_set_sample_format(formats[0], PA_SAMPLE_FLOAT32);
+ pa_format_info_set_rate(formats[0], SAMPLE_HZ);
+ pa_format_info_set_channels(formats[0], 1);
+
+ fprintf(stderr, "Creating stream %i\n", i);
+
+ snprintf(name, sizeof(name), "stream #%i", i);
+
+ streams[i] = pa_stream_new_extended(c, name, formats, 1, NULL);
+ fail_unless(streams[i] != NULL);
+ pa_stream_set_state_callback(streams[i], stream_state_callback, (void*) (long) i);
+ pa_stream_connect_playback(streams[i], NULL, &buffer_attr, PA_STREAM_START_CORKED, NULL, i == 0 ? NULL : streams[0]);
+
+ pa_format_info_free(formats[0]);
+ }
+
+ break;
+ }
+
+ case PA_CONTEXT_TERMINATED:
+ mainloop_api->quit(mainloop_api, 0);
+ break;
+
+ case PA_CONTEXT_FAILED:
+ default:
+ fprintf(stderr, "Context error: %s\n", pa_strerror(pa_context_errno(c)));
+ ck_abort();
+ }
+}
+
+START_TEST (extended_test) {
+ pa_mainloop* m = NULL;
+ int i, ret = 1;
+
+ for (i = 0; i < SAMPLE_HZ; i++)
+ data[i] = (float) sin(((double) i/SAMPLE_HZ)*2*M_PI*SINE_HZ)/2;
+
+ for (i = 0; i < NSTREAMS; i++)
+ streams[i] = NULL;
+
+ /* Set up a new main loop */
+ m = pa_mainloop_new();
+ fail_unless(m != NULL);
+
+ mainloop_api = pa_mainloop_get_api(m);
+
+ context = pa_context_new(mainloop_api, bname);
+ fail_unless(context != NULL);
+
+ pa_context_set_state_callback(context, context_state_callback, NULL);
+
+ /* Connect the context */
+ if (pa_context_connect(context, NULL, 0, NULL) < 0) {
+ fprintf(stderr, "pa_context_connect() failed.\n");
+ goto quit;
+ }
+
+ if (pa_mainloop_run(m, &ret) < 0)
+ fprintf(stderr, "pa_mainloop_run() failed.\n");
+
+quit:
+ pa_context_unref(context);
+
+ for (i = 0; i < NSTREAMS; i++)
+ if (streams[i])
+ pa_stream_unref(streams[i]);
+
+ pa_mainloop_free(m);
+
+ fail_unless(ret == 0);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ bname = argv[0];
+
+ s = suite_create("Extended");
+ tc = tcase_create("extended");
+ tcase_add_test(tc, extended_test);
+ /* 4s of audio, 0.5s grace time */
+ tcase_set_timeout(tc, 4.5);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/flist-test.c b/src/tests/flist-test.c
new file mode 100644
index 0000000..3abc123
--- /dev/null
+++ b/src/tests/flist-test.c
@@ -0,0 +1,101 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <pulse/util.h>
+#include <pulse/xmalloc.h>
+#include <pulsecore/flist.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-util.h>
+
+#define THREADS_MAX 20
+
+static pa_flist *flist;
+static int quit = 0;
+
+static void spin(void) {
+ int k;
+
+ /* Spin a little */
+ k = rand() % 10000;
+ for (; k > 0; k--)
+ pa_thread_yield();
+}
+
+static void thread_func(void *data) {
+ char *s = data;
+ int n = 0;
+ int b = 1;
+
+ while (!quit) {
+ char *text;
+
+ /* Allocate some memory, if possible take it from the flist */
+ if (b && (text = pa_flist_pop(flist)))
+ pa_log("%s: popped '%s'", s, text);
+ else {
+ text = pa_sprintf_malloc("Block %i, allocated by %s", n++, s);
+ pa_log("%s: allocated '%s'", s, text);
+ }
+
+ b = !b;
+
+ spin();
+
+ /* Give it back to the flist if possible */
+ if (pa_flist_push(flist, text) < 0) {
+ pa_log("%s: failed to push back '%s'", s, text);
+ pa_xfree(text);
+ } else
+ pa_log("%s: pushed", s);
+
+ spin();
+ }
+
+ if (pa_flist_push(flist, s) < 0)
+ pa_xfree(s);
+}
+
+int main(int argc, char* argv[]) {
+ pa_thread *threads[THREADS_MAX];
+ int i;
+
+ flist = pa_flist_new(0);
+
+ for (i = 0; i < THREADS_MAX; i++) {
+ threads[i] = pa_thread_new("test", thread_func, pa_sprintf_malloc("Thread #%i", i+1));
+ pa_assert(threads[i]);
+ }
+
+ pa_msleep(60000);
+ quit = 1;
+
+ for (i = 0; i < THREADS_MAX; i++)
+ pa_thread_free(threads[i]);
+
+ pa_flist_free(flist, pa_xfree);
+
+ return 0;
+}
diff --git a/src/tests/format-test.c b/src/tests/format-test.c
new file mode 100644
index 0000000..20869da
--- /dev/null
+++ b/src/tests/format-test.c
@@ -0,0 +1,169 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+
+#include <check.h>
+
+#include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
+#include <pulse/format.h>
+#include <pulse/xmalloc.h>
+
+#define INIT(f) f = pa_format_info_new()
+#define DEINIT(f) pa_format_info_free(f);
+#define REINIT(f) { DEINIT(f); INIT(f); }
+
+START_TEST (format_test) {
+ pa_format_info *f1 = NULL, *f2 = NULL;
+ int rates1[] = { 32000, 44100, 48000 }, i, temp_int1 = -1, PA_UNUSED temp_int2 = -1, *temp_int_array;
+ const char *strings[] = { "thing1", "thing2", "thing3" };
+ char *temp_str, **temp_str_array;
+
+ /* 1. Simple fixed format int check */
+ INIT(f1); INIT(f2);
+ f1->encoding = PA_ENCODING_AC3_IEC61937;
+ pa_format_info_set_prop_int(f1, PA_PROP_FORMAT_RATE, 32000);
+ f2->encoding = PA_ENCODING_AC3_IEC61937;
+ pa_format_info_set_prop_int(f2, PA_PROP_FORMAT_RATE, 44100);
+ fail_unless(!pa_format_info_is_compatible(f1, f2));
+
+ /* 2. Check int array membership - positive */
+ REINIT(f1); REINIT(f2);
+ f1->encoding = PA_ENCODING_AC3_IEC61937;
+ pa_format_info_set_prop_int_array(f1, PA_PROP_FORMAT_RATE, rates1, PA_ELEMENTSOF(rates1));
+ f2->encoding = PA_ENCODING_AC3_IEC61937;
+ pa_format_info_set_prop_int(f2, PA_PROP_FORMAT_RATE, 44100);
+ fail_unless(pa_format_info_is_compatible(f1, f2));
+ fail_unless(pa_format_info_is_compatible(f2, f1));
+
+ /* 3. Check int array membership - negative */
+ REINIT(f2);
+ f2->encoding = PA_ENCODING_AC3_IEC61937;
+ pa_format_info_set_prop_int(f2, PA_PROP_FORMAT_RATE, 96000);
+ fail_unless(!pa_format_info_is_compatible(f1, f2));
+ fail_unless(!pa_format_info_is_compatible(f2, f1));
+
+ /* 4. Check int range - positive */
+ REINIT(f1); REINIT(f2);
+ f1->encoding = PA_ENCODING_AC3_IEC61937;
+ pa_format_info_set_prop_int_range(f1, PA_PROP_FORMAT_RATE, 32000, 48000);
+ f2->encoding = PA_ENCODING_AC3_IEC61937;
+ pa_format_info_set_prop_int(f2, PA_PROP_FORMAT_RATE, 44100);
+ fail_unless(pa_format_info_is_compatible(f1, f2));
+ fail_unless(pa_format_info_is_compatible(f2, f1));
+
+ /* 5. Check int range - negative */
+ REINIT(f2);
+ f2->encoding = PA_ENCODING_AC3_IEC61937;
+ pa_format_info_set_prop_int(f2, PA_PROP_FORMAT_RATE, 96000);
+ fail_unless(!pa_format_info_is_compatible(f1, f2));
+ fail_unless(!pa_format_info_is_compatible(f2, f1));
+
+ /* 6. Simple fixed format string check */
+ REINIT(f1); REINIT(f2);
+ f1->encoding = PA_ENCODING_AC3_IEC61937;
+ pa_format_info_set_prop_string(f1, "format.test_string", "thing1");
+ f2->encoding = PA_ENCODING_AC3_IEC61937;
+ pa_format_info_set_prop_string(f2, "format.test_string", "notthing1");
+ fail_unless(!pa_format_info_is_compatible(f1, f2));
+
+ /* 7. Check string array membership - positive */
+ REINIT(f1); REINIT(f2);
+ f1->encoding = PA_ENCODING_AC3_IEC61937;
+ pa_format_info_set_prop_string_array(f1, "format.test_string", strings, PA_ELEMENTSOF(strings));
+ f2->encoding = PA_ENCODING_AC3_IEC61937;
+ pa_format_info_set_prop_string(f2, "format.test_string", "thing3");
+ fail_unless(pa_format_info_is_compatible(f1, f2));
+ fail_unless(pa_format_info_is_compatible(f2, f1));
+
+ /* 8. Check string array membership - negative */
+ REINIT(f2);
+ f2->encoding = PA_ENCODING_AC3_IEC61937;
+ pa_format_info_set_prop_string(f2, "format.test_string", "thing5");
+ fail_unless(!pa_format_info_is_compatible(f1, f2));
+ fail_unless(!pa_format_info_is_compatible(f2, f1));
+
+ /* 9. Verify setting/getting an int */
+ REINIT(f1);
+ pa_format_info_set_prop_int(f1, "format.test_string", 42);
+ fail_unless(pa_format_info_get_prop_type(f1, "format.test_string") == PA_PROP_TYPE_INT);
+ fail_unless(pa_format_info_get_prop_int(f1, "format.test_string", &temp_int1) == 0);
+ fail_unless(temp_int1 == 42);
+
+ /* 10. Verify setting/getting an int range */
+ REINIT(f1);
+ pa_format_info_set_prop_int_range(f1, "format.test_string", 0, 100);
+ pa_assert(pa_format_info_get_prop_type(f1, "format.test_string") == PA_PROP_TYPE_INT_RANGE);
+ pa_assert(pa_format_info_get_prop_int_range(f1, "format.test_string", &temp_int1, &temp_int2) == 0);
+ pa_assert(temp_int1 == 0 && temp_int2 == 100);
+
+ /* 11. Verify setting/getting an int array */
+ REINIT(f1);
+ pa_format_info_set_prop_int_array(f1, "format.test_string", rates1, PA_ELEMENTSOF(rates1));
+ fail_unless(pa_format_info_get_prop_type(f1, "format.test_string") == PA_PROP_TYPE_INT_ARRAY);
+ fail_unless(pa_format_info_get_prop_int_array(f1, "format.test_string", &temp_int_array, &temp_int1) == 0);
+ fail_unless(temp_int1 == PA_ELEMENTSOF(rates1));
+ for (i = 0; i < temp_int1; i++)
+ fail_unless(temp_int_array[i] == rates1[i]);
+ pa_xfree(temp_int_array);
+
+ /* 12. Verify setting/getting a string */
+ REINIT(f1);
+ pa_format_info_set_prop_string(f1, "format.test_string", "foo");
+ fail_unless(pa_format_info_get_prop_type(f1, "format.test_string") == PA_PROP_TYPE_STRING);
+ fail_unless(pa_format_info_get_prop_string(f1, "format.test_string", &temp_str) == 0);
+ fail_unless(pa_streq(temp_str, "foo"));
+ pa_xfree(temp_str);
+
+ /* 13. Verify setting/getting an int array */
+ REINIT(f1);
+ pa_format_info_set_prop_string_array(f1, "format.test_string", strings, PA_ELEMENTSOF(strings));
+ fail_unless(pa_format_info_get_prop_type(f1, "format.test_string") == PA_PROP_TYPE_STRING_ARRAY);
+ fail_unless(pa_format_info_get_prop_string_array(f1, "format.test_string", &temp_str_array, &temp_int1) == 0);
+ fail_unless(temp_int1 == PA_ELEMENTSOF(strings));
+ for (i = 0; i < temp_int1; i++)
+ fail_unless(pa_streq(temp_str_array[i], strings[i]));
+ pa_format_info_free_string_array(temp_str_array, temp_int1);
+
+ DEINIT(f1);
+ DEINIT(f2);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ s = suite_create("Format");
+ tc = tcase_create("format");
+ tcase_add_test(tc, format_test);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/get-binary-name-test.c b/src/tests/get-binary-name-test.c
new file mode 100644
index 0000000..cd53bde
--- /dev/null
+++ b/src/tests/get-binary-name-test.c
@@ -0,0 +1,74 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+
+#include <check.h>
+
+#include <pulse/util.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/log.h>
+
+START_TEST (getbinaryname_test) {
+ char *exename;
+ size_t allocated = 128;
+
+ for (;;) {
+ exename = pa_xmalloc(allocated);
+
+ if (!pa_get_binary_name(exename, allocated)) {
+ pa_log_error("failed to read binary name");
+ pa_xfree(exename);
+ ck_abort();
+ }
+
+ if (strlen(exename) < allocated - 1) {
+ pa_log("%s", exename);
+ pa_xfree(exename);
+ return;
+ }
+
+ pa_xfree(exename);
+ allocated *= 2;
+ }
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ s = suite_create("Binary Name");
+ tc = tcase_create("getbinaryname");
+ tcase_add_test(tc, getbinaryname_test);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/gtk-test.c b/src/tests/gtk-test.c
new file mode 100644
index 0000000..8edc5f2
--- /dev/null
+++ b/src/tests/gtk-test.c
@@ -0,0 +1,83 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#pragma GCC diagnostic ignored "-Wstrict-prototypes"
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+#include <glib.h>
+
+#include <pulse/context.h>
+#include <pulse/glib-mainloop.h>
+
+pa_context *ctxt;
+pa_glib_mainloop *m;
+
+static void context_state_callback(pa_context *c, void *userdata);
+
+static void connect(void) {
+ int r;
+
+ ctxt = pa_context_new(pa_glib_mainloop_get_api(m), NULL);
+ g_assert(ctxt);
+
+ r = pa_context_connect(ctxt, NULL, PA_CONTEXT_NOAUTOSPAWN|PA_CONTEXT_NOFAIL, NULL);
+ g_assert(r == 0);
+
+ pa_context_set_state_callback(ctxt, context_state_callback, NULL);
+}
+
+static void context_state_callback(pa_context *c, void *userdata) {
+ switch (pa_context_get_state(c)) {
+ case PA_CONTEXT_FAILED:
+ pa_context_unref(ctxt);
+ ctxt = NULL;
+ connect();
+ break;
+ default:
+ break;
+ }
+}
+
+int main(int argc, char *argv[]) {
+
+ GtkWidget *window;
+
+ gtk_init(&argc, &argv);
+
+ g_set_application_name("This is a test");
+ gtk_window_set_default_icon_name("foobar");
+ g_setenv("PULSE_PROP_media.role", "phone", TRUE);
+
+ window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title(GTK_WINDOW (window), g_get_application_name());
+ gtk_widget_show_all(window);
+
+ m = pa_glib_mainloop_new(NULL);
+ g_assert(m);
+
+ connect();
+ gtk_main();
+
+ pa_context_unref(ctxt);
+ pa_glib_mainloop_free(m);
+
+ return 0;
+}
diff --git a/src/tests/hashmap-test.c b/src/tests/hashmap-test.c
new file mode 100644
index 0000000..f460bd4
--- /dev/null
+++ b/src/tests/hashmap-test.c
@@ -0,0 +1,260 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <check.h>
+#include <pulsecore/hashmap.h>
+
+struct int_entry {
+ int key;
+ int value;
+};
+
+static unsigned int_trivial_hash_func(const void* pa) {
+ int a = *((unsigned*) pa);
+ if (a < 0) {
+ return -a;
+ }
+ return a;
+}
+
+static int int_compare_func(const void* pa, const void* pb) {
+ int a, b;
+ a = *((int*) pa);
+ b = *((int*) pb);
+ if (a < b) {
+ return -1;
+ }
+ if (a == b) {
+ return 0;
+ }
+ return 1;
+}
+
+/* single_key_test exercises basic hashmap functionality on a single key. */
+START_TEST(single_key_test)
+ {
+ pa_hashmap* map;
+ struct int_entry entry;
+ int lookup_key;
+ int put_ret;
+ void* get_ret;
+ unsigned size_ret;
+
+ entry.key = 0;
+ entry.value = 0;
+
+ lookup_key = 0;
+
+ map = pa_hashmap_new(int_trivial_hash_func, int_compare_func);
+
+ if ((put_ret = pa_hashmap_put(map, &entry.key, &entry)) != 0) {
+ ck_abort_msg("Hashmap rejected k=0/v=0; got %d, want %d", put_ret, 0);
+ }
+
+ if ((size_ret = pa_hashmap_size(map)) != 1) {
+ ck_abort_msg("Hashmap reported wrong size; got %u, want 1", size_ret);
+ }
+
+ if ((get_ret = pa_hashmap_get(map, &lookup_key)) != &entry) {
+ ck_abort_msg("Got wrong value from hashmap for k=0; got %p, want %p", get_ret, &entry);
+ }
+
+ if ((put_ret = pa_hashmap_put(map, &entry.key, &entry)) == 0) {
+ ck_abort_msg("Hashmap allowed duplicate key for k=0; got %d, want non-zero", put_ret);
+ }
+
+ if ((size_ret = pa_hashmap_size(map)) != 1) {
+ ck_abort_msg("Hashmap reported wrong size; got %u, want 1", size_ret);
+ }
+
+ if ((get_ret = pa_hashmap_remove(map, &lookup_key)) != &entry) {
+ ck_abort_msg("Hashmap returned wrong value during free; got %p, want %p", get_ret, &entry);
+ }
+
+ if ((size_ret = pa_hashmap_size(map)) != 0) {
+ ck_abort_msg("Hashmap reported wrong size; got %u, want 1", size_ret);
+ }
+
+ pa_hashmap_free(map);
+ }
+END_TEST
+
+/* remove_all_test checks that pa_hashmap_remove_all really removes all entries
+ * from the map.*/
+START_TEST(remove_all_test)
+ {
+ pa_hashmap* map;
+ struct int_entry entries[1000];
+ unsigned size;
+
+ for (int i = 0; i < 1000; i++) {
+ entries[i].key = i;
+ entries[i].value = i;
+ }
+
+ map = pa_hashmap_new(int_trivial_hash_func, int_compare_func);
+
+ for (int i = 0; i < 1000; i++) {
+ pa_hashmap_put(map, &entries[i].key, &entries[i]);
+ }
+
+ if ((size = pa_hashmap_size(map)) != 1000) {
+ ck_abort_msg("Hashmap has wrong size; got %u, want 1000", size);
+ }
+
+ pa_hashmap_remove_all(map);
+
+ if ((size = pa_hashmap_size(map)) != 0) {
+ ck_abort_msg("Hashmap has wrong size; got %u, want 0", size);
+ }
+
+ pa_hashmap_free(map);
+ }
+END_TEST
+
+/* fill_all_buckets hits the hashmap with enough keys to exercise the bucket
+ * linked list for every bucket. */
+START_TEST(fill_all_buckets)
+ {
+ pa_hashmap* map;
+ struct int_entry entries[1000];
+ int lookup_keys[1000]; /* Don't share addresses with insertion keys */
+
+ map = pa_hashmap_new(int_trivial_hash_func, int_compare_func);
+
+ for (int i = 0; i < 1000; i++) {
+ entries[i].key = i;
+ lookup_keys[i] = i;
+ entries[i].value = i;
+ }
+
+ for (int i = 0; i < 1000; i++) {
+ int put_ret;
+ unsigned size_ret;
+
+ if ((put_ret = pa_hashmap_put(map, &entries[i].key, &entries[i])) != 0) {
+ ck_abort_msg("Unexpected failure putting k=%d v=%d into the map", entries[i].key, entries[i].value);
+ }
+
+ if ((size_ret = pa_hashmap_size(map)) != i + 1) {
+ ck_abort_msg("Hashmap reported wrong size; got %u, want %d", size_ret, i);
+ }
+ }
+
+ for (int i = 0; i < 1000; i++) {
+ unsigned size_ret;
+ int* k;
+ struct int_entry* v;
+
+ k = lookup_keys + i;
+
+ v = (struct int_entry*) pa_hashmap_remove(map, k);
+ if (v == NULL) {
+ ck_abort_msg("Hashmap returned NULL for k=%d; wanted nonnull", *k);
+ }
+ if ((*v).value != i) {
+ ck_abort_msg("Hashmap returned wrong value for k=%d; got %d, want %d", *k, (*v).value, i);
+ }
+
+ if ((size_ret = pa_hashmap_size(map)) != 1000 - i - 1) {
+ ck_abort_msg("Hashmap reported wrong size; got %u, want %d", size_ret, 1000 - i);
+ }
+ }
+
+ pa_hashmap_free(map);
+ }
+END_TEST
+
+/* iterate_test exercises the iteration list maintained by the hashtable. */
+START_TEST(iterate_test)
+ {
+ pa_hashmap* map;
+ struct int_entry entries[1000];
+ void* state;
+ struct int_entry* v;
+ int expected;
+
+ for (int i = 0; i < 1000; i++) {
+ entries[i].key = i;
+ entries[i].value = i;
+ }
+
+ map = pa_hashmap_new(int_trivial_hash_func, int_compare_func);
+
+ for (int i = 0; i < 1000; i++) {
+ if (pa_hashmap_put(map, &(entries[i].key), &(entries[i])) != 0) {
+ ck_abort_msg("Unexpected failure putting k=%d v=%d into the map", entries[i].key, entries[i].value);
+ }
+ }
+
+ expected = 0;
+ PA_HASHMAP_FOREACH (v, map, state) {
+ if ((*v).value != expected) {
+ ck_abort_msg("Got bad order iterating over hashmap: got %d, want %d", v->value, expected);
+ }
+ expected++;
+ }
+
+ expected = 999;
+ PA_HASHMAP_FOREACH_BACKWARDS (v, map, state) {
+ if ((*v).value != expected) {
+ ck_abort_msg("Got bad order iterating over hashmap: got %d, want %d", v->value, expected);
+ }
+ expected--;
+ }
+
+ /* Now empty out the hashmap. The iteration list should be empty. */
+ for(int i = 0; i < 1000; i++) {
+ pa_hashmap_remove(map, &(entries[i].key));
+ }
+
+ PA_HASHMAP_FOREACH(v, map, state) {
+ ck_abort_msg("Iteration over empty map returned entries");
+ }
+
+ /* Now add one element back. The iteration list should only contain this
+ * one element, even though the entry nodes are reused. */
+ if(pa_hashmap_put(map, &(entries[0].key), &(entries[0])) != 0) {
+ ck_abort_msg("Unexpected failure putting k=%d v=%d into the map", entries[0].key, entries[0].value);
+ }
+
+ expected = 0;
+ PA_HASHMAP_FOREACH(v, map, state) {
+ if ((*v).value != expected) {
+ ck_abort_msg("Got bad order iterating over hashmap: got %d, want %d", v->value, expected);
+ }
+ expected++;
+ }
+ if (expected != 1) {
+ ck_abort_msg("Got too many elements while iterating: got %d, want 1", expected);
+ }
+
+ pa_hashmap_free(map);
+ }
+END_TEST
+
+int main(int argc, char** argv) {
+ int failed = 0;
+ Suite* s;
+ TCase* tc;
+ SRunner* sr;
+
+ s = suite_create("HashMap");
+ tc = tcase_create("hashmap");
+ tcase_add_test(tc, single_key_test);
+ tcase_add_test(tc, remove_all_test);
+ tcase_add_test(tc, fill_all_buckets);
+ tcase_add_test(tc, iterate_test);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ if (failed > 0) {
+ return EXIT_FAILURE;
+ }
+ return EXIT_SUCCESS;
+}
diff --git a/src/tests/hook-list-test.c b/src/tests/hook-list-test.c
new file mode 100644
index 0000000..aec2a5d
--- /dev/null
+++ b/src/tests/hook-list-test.c
@@ -0,0 +1,61 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <check.h>
+
+#include <pulsecore/hook-list.h>
+#include <pulsecore/log.h>
+
+static pa_hook_result_t func1(const char *hook_data, const char *call_data, const char *slot_data) {
+ pa_log("(func1) hook=%s call=%s slot=%s", hook_data, call_data, slot_data);
+ /* succeed when it runs to here */
+ fail_unless(1);
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t func2(const char *hook_data, const char *call_data, const char *slot_data) {
+ pa_log("(func2) hook=%s call=%s slot=%s", hook_data, call_data, slot_data);
+ /* succeed when it runs to here */
+ fail_unless(1);
+ return PA_HOOK_OK;
+}
+
+START_TEST (hooklist_test) {
+ pa_hook hook;
+ pa_hook_slot *slot;
+
+ pa_hook_init(&hook, (void*) "hook");
+
+ pa_hook_connect(&hook, PA_HOOK_LATE, (pa_hook_cb_t) func1, (void*) "slot1");
+ slot = pa_hook_connect(&hook, PA_HOOK_NORMAL, (pa_hook_cb_t) func2, (void*) "slot2");
+ pa_hook_connect(&hook, PA_HOOK_NORMAL, (pa_hook_cb_t) func1, (void*) "slot3");
+
+ pa_hook_fire(&hook, (void*) "call1");
+
+ pa_hook_slot_free(slot);
+
+ pa_hook_fire(&hook, (void*) "call2");
+
+ pa_hook_done(&hook);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ s = suite_create("Hook List");
+ tc = tcase_create("hooklist");
+ tcase_add_test(tc, hooklist_test);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/interpol-test.c b/src/tests/interpol-test.c
new file mode 100644
index 0000000..bb69e52
--- /dev/null
+++ b/src/tests/interpol-test.c
@@ -0,0 +1,294 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <check.h>
+
+#include <pulse/pulseaudio.h>
+#include <pulse/mainloop.h>
+
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/thread.h>
+
+#define INTERPOLATE
+//#define CORK
+
+static pa_context *context = NULL;
+static pa_stream *stream = NULL;
+static pa_mainloop_api *mainloop_api = NULL;
+static bool playback = true;
+static pa_usec_t latency = 0;
+static const char *bname = NULL;
+
+static void stream_write_cb(pa_stream *p, size_t nbytes, void *userdata) {
+ /* Just some silence */
+
+ for (;;) {
+ void *data;
+
+ fail_unless((nbytes = pa_stream_writable_size(p)) != (size_t) -1);
+
+ if (nbytes <= 0)
+ break;
+
+ fail_unless(pa_stream_begin_write(p, &data, &nbytes) == 0);
+ pa_memzero(data, nbytes);
+ fail_unless(pa_stream_write(p, data, nbytes, NULL, 0, PA_SEEK_RELATIVE) == 0);
+ }
+}
+
+static void stream_read_cb(pa_stream *p, size_t nbytes, void *userdata) {
+ /* We don't care about the data, just drop it */
+
+ for (;;) {
+ const void *data;
+
+ pa_assert_se((nbytes = pa_stream_readable_size(p)) != (size_t) -1);
+
+ if (nbytes <= 0)
+ break;
+
+ fail_unless(pa_stream_peek(p, &data, &nbytes) == 0);
+ fail_unless(pa_stream_drop(p) == 0);
+ }
+}
+
+static void stream_latency_cb(pa_stream *p, void *userdata) {
+#ifndef INTERPOLATE
+ pa_operation *o;
+
+ o = pa_stream_update_timing_info(p, NULL, NULL);
+ pa_operation_unref(o);
+#endif
+}
+
+/* This is called whenever the context status changes */
+static void context_state_callback(pa_context *c, void *userdata) {
+ fail_unless(c != NULL);
+
+ switch (pa_context_get_state(c)) {
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+
+ case PA_CONTEXT_READY: {
+ pa_stream_flags_t flags = PA_STREAM_AUTO_TIMING_UPDATE;
+ pa_buffer_attr attr;
+ static const pa_sample_spec ss = {
+ .format = PA_SAMPLE_S16LE,
+ .rate = 44100,
+ .channels = 2
+ };
+
+ pa_zero(attr);
+ attr.maxlength = (uint32_t) -1;
+ attr.tlength = latency > 0 ? (uint32_t) pa_usec_to_bytes(latency, &ss) : (uint32_t) -1;
+ attr.prebuf = (uint32_t) -1;
+ attr.minreq = (uint32_t) -1;
+ attr.fragsize = (uint32_t) -1;
+
+#ifdef INTERPOLATE
+ flags |= PA_STREAM_INTERPOLATE_TIMING;
+#endif
+
+ if (latency > 0)
+ flags |= PA_STREAM_ADJUST_LATENCY;
+
+ pa_log("Connection established");
+
+ stream = pa_stream_new(c, "interpol-test", &ss, NULL);
+ fail_unless(stream != NULL);
+
+ if (playback) {
+ pa_assert_se(pa_stream_connect_playback(stream, NULL, &attr, flags, NULL, NULL) == 0);
+ pa_stream_set_write_callback(stream, stream_write_cb, NULL);
+ } else {
+ pa_assert_se(pa_stream_connect_record(stream, NULL, &attr, flags) == 0);
+ pa_stream_set_read_callback(stream, stream_read_cb, NULL);
+ }
+
+ pa_stream_set_latency_update_callback(stream, stream_latency_cb, NULL);
+
+ break;
+ }
+
+ case PA_CONTEXT_TERMINATED:
+ break;
+
+ case PA_CONTEXT_FAILED:
+ default:
+ pa_log_error("Context error: %s", pa_strerror(pa_context_errno(c)));
+ ck_abort();
+ }
+}
+
+START_TEST (interpol_test) {
+ pa_threaded_mainloop* m = NULL;
+ int k;
+ struct timeval start, last_info = { 0, 0 };
+ pa_usec_t old_t = 0, old_rtc = 0;
+#ifdef CORK
+ bool corked = false;
+#endif
+
+ /* Set up a new main loop */
+ m = pa_threaded_mainloop_new();
+ fail_unless(m != NULL);
+ mainloop_api = pa_threaded_mainloop_get_api(m);
+ fail_unless(mainloop_api != NULL);
+ context = pa_context_new(mainloop_api, bname);
+ fail_unless(context != NULL);
+
+ pa_context_set_state_callback(context, context_state_callback, NULL);
+
+ fail_unless(pa_context_connect(context, NULL, 0, NULL) >= 0);
+
+ pa_gettimeofday(&start);
+
+ fail_unless(pa_threaded_mainloop_start(m) >= 0);
+
+/* #ifdef CORK */
+ for (k = 0; k < 20000; k++)
+/* #else */
+/* for (k = 0; k < 2000; k++) */
+/* #endif */
+ {
+ bool success = false, changed = false;
+ pa_usec_t t, rtc, d;
+ struct timeval now, tv;
+ bool playing = false;
+
+ pa_threaded_mainloop_lock(m);
+
+ if (stream) {
+ const pa_timing_info *info;
+
+ if (pa_stream_get_time(stream, &t) >= 0 &&
+ pa_stream_get_latency(stream, &d, NULL) >= 0)
+ success = true;
+
+ if ((info = pa_stream_get_timing_info(stream))) {
+ if (memcmp(&last_info, &info->timestamp, sizeof(struct timeval))) {
+ changed = true;
+ last_info = info->timestamp;
+ }
+ if (info->playing)
+ playing = true;
+ }
+ }
+
+ pa_threaded_mainloop_unlock(m);
+
+ pa_gettimeofday(&now);
+
+ if (success) {
+#ifdef CORK
+ bool cork_now;
+#endif
+ rtc = pa_timeval_diff(&now, &start);
+ pa_log_info("%i\t%llu\t%llu\t%llu\t%llu\t%lli\t%u\t%u\t%llu\t%llu", k,
+ (unsigned long long) rtc,
+ (unsigned long long) t,
+ (unsigned long long) (rtc-old_rtc),
+ (unsigned long long) (t-old_t),
+ (signed long long) rtc - (signed long long) t,
+ changed,
+ playing,
+ (unsigned long long) latency,
+ (unsigned long long) d);
+
+ fflush(stdout);
+ old_t = t;
+ old_rtc = rtc;
+
+#ifdef CORK
+ cork_now = (rtc / (2*PA_USEC_PER_SEC)) % 2 == 1;
+
+ if (corked != cork_now) {
+ pa_threaded_mainloop_lock(m);
+ pa_operation_unref(pa_stream_cork(stream, cork_now, NULL, NULL));
+ pa_threaded_mainloop_unlock(m);
+
+ pa_log(cork_now ? "Corking" : "Uncorking");
+
+ corked = cork_now;
+ }
+#endif
+ }
+
+ /* Spin loop, ugly but normal usleep() is just too badly grained */
+ tv = now;
+ while (pa_timeval_diff(pa_gettimeofday(&now), &tv) < 1000)
+ pa_thread_yield();
+ }
+
+ if (m)
+ pa_threaded_mainloop_stop(m);
+
+ if (stream) {
+ pa_stream_disconnect(stream);
+ pa_stream_unref(stream);
+ }
+
+ if (context) {
+ pa_context_disconnect(context);
+ pa_context_unref(context);
+ }
+
+ if (m)
+ pa_threaded_mainloop_free(m);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ if (!getenv("MAKE_CHECK"))
+ pa_log_set_level(PA_LOG_DEBUG);
+
+ bname = argv[0];
+ playback = argc <= 1 || !pa_streq(argv[1], "-r");
+ latency = (argc >= 2 && !pa_streq(argv[1], "-r")) ? atoi(argv[1]) : (argc >= 3 ? atoi(argv[2]) : 0);
+
+ s = suite_create("Interpol");
+ tc = tcase_create("interpol");
+ tcase_add_test(tc, interpol_test);
+ tcase_set_timeout(tc, 5 * 60);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/ipacl-test.c b/src/tests/ipacl-test.c
new file mode 100644
index 0000000..4ce3490
--- /dev/null
+++ b/src/tests/ipacl-test.c
@@ -0,0 +1,116 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <check.h>
+
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#ifdef HAVE_NETINET_IN_SYSTM_H
+#include <netinet/in_systm.h>
+#endif
+#ifdef HAVE_NETINET_IP_H
+#include <netinet/ip.h>
+#endif
+
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/socket.h>
+#include <pulsecore/ipacl.h>
+#include <pulsecore/arpa-inet.h>
+
+static void do_ip_acl_check(const char *s, int fd, int expected) {
+ pa_ip_acl *acl;
+ int result;
+
+ acl = pa_ip_acl_new(s);
+ fail_unless(acl != NULL);
+ result = pa_ip_acl_check(acl, fd);
+ pa_ip_acl_free(acl);
+
+ pa_log_info("%-20s result=%u (should be %u)", s, result, expected);
+ fail_unless(result == expected);
+}
+
+START_TEST (ipacl_test) {
+ struct sockaddr_in sa;
+#ifdef HAVE_IPV6
+ struct sockaddr_in6 sa6;
+#endif
+ int fd;
+ int r;
+
+ fd = socket(PF_INET, SOCK_STREAM, 0);
+ fail_unless(fd >= 0);
+
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(22);
+ sa.sin_addr.s_addr = inet_addr("127.0.0.1");
+
+ r = connect(fd, (struct sockaddr*) &sa, sizeof(sa));
+ fail_unless(r >= 0);
+
+ do_ip_acl_check("127.0.0.1", fd, 1);
+ do_ip_acl_check("127.0.0.2/0", fd, 1);
+ do_ip_acl_check("127.0.0.1/32", fd, 1);
+ do_ip_acl_check("127.0.0.1/7", fd, 1);
+ do_ip_acl_check("127.0.0.2", fd, 0);
+ do_ip_acl_check("127.0.0.0/8;0.0.0.0/32", fd, 1);
+ do_ip_acl_check("128.0.0.2/9", fd, 0);
+ do_ip_acl_check("::1/9", fd, 0);
+
+ close(fd);
+
+#ifdef HAVE_IPV6
+ if ( (fd = socket(PF_INET6, SOCK_STREAM, 0)) < 0 ) {
+ pa_log_error("Unable to open IPv6 socket, IPv6 tests ignored");
+ return;
+ }
+
+ memset(&sa6, 0, sizeof(sa6));
+ sa6.sin6_family = AF_INET6;
+ sa6.sin6_port = htons(22);
+ fail_unless(inet_pton(AF_INET6, "::1", &sa6.sin6_addr) == 1);
+
+ r = connect(fd, (struct sockaddr*) &sa6, sizeof(sa6));
+ fail_unless(r >= 0);
+
+ do_ip_acl_check("::1", fd, 1);
+ do_ip_acl_check("::1/9", fd, 1);
+ do_ip_acl_check("::/0", fd, 1);
+ do_ip_acl_check("::2/128", fd, 0);
+ do_ip_acl_check("::2/127", fd, 0);
+ do_ip_acl_check("::2/126", fd, 1);
+
+ close(fd);
+#endif
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ if (!getenv("MAKE_CHECK"))
+ pa_log_set_level(PA_LOG_DEBUG);
+
+ s = suite_create("IP ACL");
+ tc = tcase_create("ipacl");
+ tcase_add_test(tc, ipacl_test);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/json-test.c b/src/tests/json-test.c
new file mode 100644
index 0000000..0894a30
--- /dev/null
+++ b/src/tests/json-test.c
@@ -0,0 +1,284 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2016 Arun Raghavan <mail@arunraghavan.net>
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <check.h>
+
+#include <pulse/json.h>
+#include <pulsecore/core-util.h>
+
+START_TEST (string_test) {
+ pa_json_object *o;
+ unsigned int i;
+ const char *strings_parse[] = {
+ "\"\"", "\"test\"", "\"test123\"", "\"123\"", "\"newline\\n\"", "\" spaces \"",
+ " \"lots of spaces\" ", "\"esc\\nape\"", "\"escape a \\\" quote\"",
+ };
+ const char *strings_compare[] = {
+ "", "test", "test123", "123", "newline\n", " spaces ",
+ "lots of spaces", "esc\nape", "escape a \" quote",
+ };
+
+ for (i = 0; i < PA_ELEMENTSOF(strings_parse); i++) {
+ o = pa_json_parse(strings_parse[i]);
+
+ fail_unless(o != NULL);
+ fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_STRING);
+ fail_unless(pa_streq(pa_json_object_get_string(o), strings_compare[i]));
+
+ pa_json_object_free(o);
+ }
+}
+END_TEST
+
+START_TEST(int_test) {
+ pa_json_object *o;
+ unsigned int i;
+ const char *ints_parse[] = { "1", "-1", "1234", "0" };
+ const int ints_compare[] = { 1, -1, 1234, 0 };
+
+ for (i = 0; i < PA_ELEMENTSOF(ints_parse); i++) {
+ o = pa_json_parse(ints_parse[i]);
+
+ fail_unless(o != NULL);
+ fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_INT);
+ fail_unless(pa_json_object_get_int(o) == ints_compare[i]);
+
+ pa_json_object_free(o);
+ }
+}
+END_TEST
+
+START_TEST(double_test) {
+ pa_json_object *o;
+ unsigned int i;
+ const char *doubles_parse[] = {
+ "1.0", "-1.1", "1234e2", "1234e0", "0.1234", "-0.1234", "1234e-1", "1234.5e-1", "1234.5e+2",
+ };
+ const double doubles_compare[] = {
+ 1.0, -1.1, 123400.0, 1234.0, 0.1234, -0.1234, 123.4, 123.45, 123450.0,
+ };
+
+ for (i = 0; i < PA_ELEMENTSOF(doubles_parse); i++) {
+ o = pa_json_parse(doubles_parse[i]);
+
+ fail_unless(o != NULL);
+ fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_DOUBLE);
+ fail_unless(PA_DOUBLE_IS_EQUAL(pa_json_object_get_double(o), doubles_compare[i]));
+
+ pa_json_object_free(o);
+ }
+}
+END_TEST
+
+START_TEST(null_test) {
+ pa_json_object *o;
+
+ o = pa_json_parse("null");
+
+ fail_unless(o != NULL);
+ fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_NULL);
+
+ pa_json_object_free(o);
+}
+END_TEST
+
+START_TEST(bool_test) {
+ pa_json_object *o;
+
+ o = pa_json_parse("true");
+
+ fail_unless(o != NULL);
+ fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_BOOL);
+ fail_unless(pa_json_object_get_bool(o) == true);
+
+ pa_json_object_free(o);
+
+ o = pa_json_parse("false");
+
+ fail_unless(o != NULL);
+ fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_BOOL);
+ fail_unless(pa_json_object_get_bool(o) == false);
+
+ pa_json_object_free(o);
+}
+END_TEST
+
+START_TEST(object_test) {
+ pa_json_object *o;
+ const pa_json_object *v;
+
+ o = pa_json_parse(" { \"name\" : \"A Person\" } ");
+
+ fail_unless(o != NULL);
+ fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_OBJECT);
+
+ v = pa_json_object_get_object_member(o, "name");
+ fail_unless(v != NULL);
+ fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_STRING);
+ fail_unless(pa_streq(pa_json_object_get_string(v), "A Person"));
+
+ pa_json_object_free(o);
+
+ o = pa_json_parse(" { \"age\" : -45.3e-0 } ");
+
+ fail_unless(o != NULL);
+ fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_OBJECT);
+
+ v = pa_json_object_get_object_member(o, "age");
+ fail_unless(v != NULL);
+ fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_DOUBLE);
+ fail_unless(PA_DOUBLE_IS_EQUAL(pa_json_object_get_double(v), -45.3));
+
+ pa_json_object_free(o);
+
+ o = pa_json_parse("{\"person\":true}");
+
+ fail_unless(o != NULL);
+ fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_OBJECT);
+
+ v = pa_json_object_get_object_member(o, "person");
+ fail_unless(v != NULL);
+ fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_BOOL);
+ fail_unless(pa_json_object_get_bool(v) == true);
+
+ pa_json_object_free(o);
+
+ o = pa_json_parse("{ \"parent\": { \"child\": false } }");
+ fail_unless(o != NULL);
+ fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_OBJECT);
+
+ v = pa_json_object_get_object_member(o, "parent");
+ fail_unless(v != NULL);
+ fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_OBJECT);
+ v = pa_json_object_get_object_member(v, "child");
+ fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_BOOL);
+ fail_unless(pa_json_object_get_bool(v) == false);
+
+ pa_json_object_free(o);
+}
+END_TEST
+
+START_TEST(array_test) {
+ pa_json_object *o;
+ const pa_json_object *v, *v2;
+
+ o = pa_json_parse(" [ ] ");
+
+ fail_unless(o != NULL);
+ fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY);
+ fail_unless(pa_json_object_get_array_length(o) == 0);
+
+ pa_json_object_free(o);
+
+ o = pa_json_parse("[\"a member\"]");
+
+ fail_unless(o != NULL);
+ fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY);
+ fail_unless(pa_json_object_get_array_length(o) == 1);
+
+ v = pa_json_object_get_array_member(o, 0);
+ fail_unless(v != NULL);
+ fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_STRING);
+ fail_unless(pa_streq(pa_json_object_get_string(v), "a member"));
+
+ pa_json_object_free(o);
+
+ o = pa_json_parse("[\"a member\", 1234.5, { \"another\": true } ]");
+
+ fail_unless(o != NULL);
+ fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY);
+ fail_unless(pa_json_object_get_array_length(o) == 3);
+
+ v = pa_json_object_get_array_member(o, 0);
+ fail_unless(v != NULL);
+ fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_STRING);
+ fail_unless(pa_streq(pa_json_object_get_string(v), "a member"));
+ v = pa_json_object_get_array_member(o, 1);
+ fail_unless(v != NULL);
+ fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_DOUBLE);
+ fail_unless(PA_DOUBLE_IS_EQUAL(pa_json_object_get_double(v), 1234.5));
+ v = pa_json_object_get_array_member(o, 2);
+ fail_unless(v != NULL);
+ fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_OBJECT);
+ v2 =pa_json_object_get_object_member(v, "another");
+ fail_unless(v2 != NULL);
+ fail_unless(pa_json_object_get_type(v2) == PA_JSON_TYPE_BOOL);
+ fail_unless(pa_json_object_get_bool(v2) == true);
+
+ pa_json_object_free(o);
+}
+END_TEST
+
+START_TEST(bad_test) {
+ unsigned int i;
+ const char *bad_parse[] = {
+ "\"" /* Quote not closed */,
+ "123456789012345678901234567890" /* Overflow */,
+ "0.123456789012345678901234567890" /* Overflow */,
+ "1e123456789012345678901234567890" /* Overflow */,
+ "1e" /* Bad number string */,
+ "1." /* Bad number string */,
+ "1.e3" /* Bad number string */,
+ "-" /* Bad number string */,
+ "{ \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { } } } } } } } } } } } } } } } } } } } } } }" /* Nested too deep */,
+ "[ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ { \"a\": \"b\" } ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ]" /* Nested too deep */,
+ "asdf" /* Unquoted string */,
+ "{ a: true }" /* Unquoted key in object */,
+ "\" \a\"" /* Alarm is not a valid character */
+ };
+
+ for (i = 0; i < PA_ELEMENTSOF(bad_parse); i++) {
+ pa_json_object *obj;
+
+ fail_unless((obj = pa_json_parse(bad_parse[i])) == NULL);
+ if (obj)
+ pa_json_object_free(obj);
+ }
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ s = suite_create("JSON");
+ tc = tcase_create("json");
+ tcase_add_test(tc, string_test);
+ tcase_add_test(tc, int_test);
+ tcase_add_test(tc, double_test);
+ tcase_add_test(tc, null_test);
+ tcase_add_test(tc, bool_test);
+ tcase_add_test(tc, object_test);
+ tcase_add_test(tc, array_test);
+ tcase_add_test(tc, bad_test);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/lfe-filter-test.c b/src/tests/lfe-filter-test.c
new file mode 100644
index 0000000..d779e05
--- /dev/null
+++ b/src/tests/lfe-filter-test.c
@@ -0,0 +1,196 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <check.h>
+
+#include <pulse/pulseaudio.h>
+#include <pulse/sample.h>
+#include <pulsecore/memblock.h>
+
+#include <pulsecore/filter/lfe-filter.h>
+
+struct lfe_filter_test {
+ pa_lfe_filter_t *lf;
+ pa_mempool *pool;
+ pa_sample_spec *ss;
+};
+
+static uint8_t *ori_sample_ptr;
+
+#define ONE_BLOCK_SAMPLES 4096
+#define TOTAL_SAMPLES 8192
+#define TOLERANT_VARIATION 1
+
+static void save_data_block(struct lfe_filter_test *lft, void *d, pa_memblock *blk) {
+ uint8_t *dst = d, *src;
+ size_t blk_size = pa_frame_size(lft->ss) * ONE_BLOCK_SAMPLES;
+
+ src = pa_memblock_acquire(blk);
+ memcpy(dst, src, blk_size);
+ pa_memblock_release(blk);
+}
+
+static pa_memblock* generate_data_block(struct lfe_filter_test *lft, int start) {
+ pa_memblock *r;
+ uint8_t *d, *s = ori_sample_ptr;
+ size_t blk_size = pa_frame_size(lft->ss) * ONE_BLOCK_SAMPLES;
+
+ pa_assert_se(r = pa_memblock_new(lft->pool, blk_size));
+ d = pa_memblock_acquire(r);
+ memcpy(d, s + start, blk_size);
+ pa_memblock_release(r);
+
+ return r;
+}
+
+static int compare_data_block(struct lfe_filter_test *lft, void *a, void *b) {
+ int ret = 0;
+ uint32_t i;
+ uint16_t *r = a, *u = b;
+
+ pa_assert(lft->ss->format == PA_SAMPLE_S16NE);
+
+ for (i = 0; i < ONE_BLOCK_SAMPLES; i++) {
+ if (abs(*r++ - *u++) > TOLERANT_VARIATION) {
+ pa_log_error("lfe-filter-test: test failed, the output data in the position 0x%x of a block does not equal!", i);
+ ret = -1;
+ break;
+ }
+ }
+ return ret;
+}
+
+/* in this test case, we pass two blocks of sample data to lfe-filter, each
+ block contains 4096 samples, and don't let rewind_samples exceed TOTAL_SAMPLES */
+static int lfe_filter_rewind_test(struct lfe_filter_test *lft, int rewind_samples)
+{
+ int ret = -1, pos, i;
+ pa_memchunk mc;
+ uint8_t *outptr;
+ uint32_t fz = pa_frame_size(lft->ss);
+
+ if (rewind_samples > TOTAL_SAMPLES || rewind_samples < TOTAL_SAMPLES - ONE_BLOCK_SAMPLES) {
+ pa_log_error("lfe-filter-test: Please keep %d samples < rewind_samples < %d samples", TOTAL_SAMPLES - ONE_BLOCK_SAMPLES, TOTAL_SAMPLES);
+ return ret;
+ }
+
+ outptr = pa_xmalloc(fz * TOTAL_SAMPLES);
+
+ /* let lfe-filter process all samples first, and save the processed data to the temp buffer,
+ then rewind back to some position, reprocess some samples and compare the output data with
+ the processed data saved before. */
+ for (i = 0; i < TOTAL_SAMPLES / ONE_BLOCK_SAMPLES; i++) {
+ mc.memblock = generate_data_block(lft, i * ONE_BLOCK_SAMPLES * fz);
+ mc.length = pa_memblock_get_length(mc.memblock);
+ mc.index = 0;
+ pa_lfe_filter_process(lft->lf, &mc);
+ save_data_block(lft, outptr + i * ONE_BLOCK_SAMPLES * fz, mc.memblock);
+ pa_memblock_unref(mc.memblock);
+ }
+
+ pa_lfe_filter_rewind(lft->lf, rewind_samples * fz);
+ pos = (TOTAL_SAMPLES - rewind_samples) * fz;
+ mc.memblock = generate_data_block(lft, pos);
+ mc.length = pa_memblock_get_length(mc.memblock);
+ mc.index = 0;
+ pa_lfe_filter_process(lft->lf, &mc);
+ ret = compare_data_block(lft, outptr + pos, pa_memblock_acquire(mc.memblock));
+ pa_memblock_release(mc.memblock);
+ pa_memblock_unref(mc.memblock);
+
+ pa_xfree(outptr);
+
+ return ret;
+}
+
+START_TEST (lfe_filter_test) {
+ pa_sample_spec a;
+ int ret = -1;
+ unsigned i, crossover_freq = 120;
+ pa_channel_map chmapmono = {1, {PA_CHANNEL_POSITION_LFE}};
+ struct lfe_filter_test lft;
+ short *tmp_ptr;
+
+ pa_log_set_level(PA_LOG_DEBUG);
+
+ a.channels = 1;
+ a.rate = 44100;
+ a.format = PA_SAMPLE_S16NE;
+
+ lft.ss = &a;
+ pa_assert_se(lft.pool = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true));
+
+ /* We prepare pseudo-random input audio samples for lfe-filter rewind testing*/
+ ori_sample_ptr = pa_xmalloc(pa_frame_size(lft.ss) * TOTAL_SAMPLES);
+ tmp_ptr = (short *) ori_sample_ptr;
+ for (i = 0; i < pa_frame_size(lft.ss) * TOTAL_SAMPLES / sizeof(short); i++)
+ *tmp_ptr++ = random();
+
+ /* we create a lfe-filter with cutoff frequency 120Hz and max rewind time 10 seconds */
+ pa_assert_se(lft.lf = pa_lfe_filter_new(&a, &chmapmono, crossover_freq, a.rate * 10));
+ /* rewind to a block boundary */
+ ret = lfe_filter_rewind_test(&lft, ONE_BLOCK_SAMPLES);
+ if (ret)
+ pa_log_error("lfe-filer-test: rewind to block boundary test failed!!!");
+ pa_lfe_filter_free(lft.lf);
+
+ /* we create a lfe-filter with cutoff frequency 120Hz and max rewind time 10 seconds */
+ pa_assert_se(lft.lf = pa_lfe_filter_new(&a, &chmapmono, crossover_freq, a.rate * 10));
+ /* rewind to the middle position of a block */
+ ret = lfe_filter_rewind_test(&lft, ONE_BLOCK_SAMPLES + ONE_BLOCK_SAMPLES / 2);
+ if (ret)
+ pa_log_error("lfe-filer-test: rewind to middle of block test failed!!!");
+
+ pa_xfree(ori_sample_ptr);
+
+ pa_lfe_filter_free(lft.lf);
+
+ pa_mempool_unref(lft.pool);
+
+ if (!ret)
+ pa_log_debug("lfe-filter-test: tests for both rewind to block boundary and rewind to middle position of a block passed!");
+
+ fail_unless(ret == 0);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ if (!getenv("MAKE_CHECK"))
+ pa_log_set_level(PA_LOG_DEBUG);
+
+ s = suite_create("lfe-filter");
+ tc = tcase_create("lfe-filter");
+ tcase_add_test(tc, lfe_filter_test);
+ tcase_set_timeout(tc, 10);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/lo-latency-test.c b/src/tests/lo-latency-test.c
new file mode 100644
index 0000000..813b337
--- /dev/null
+++ b/src/tests/lo-latency-test.c
@@ -0,0 +1,188 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2013 Collabora Ltd.
+ Author: Arun Raghavan <arun.raghavan@collabora.co.uk>
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <check.h>
+
+#include "lo-test-util.h"
+
+#define SAMPLE_HZ 44100
+#define CHANNELS 2
+#define N_OUT (SAMPLE_HZ * 1)
+
+static float out[N_OUT][CHANNELS];
+
+pa_lo_test_context test_ctx;
+static const char *context_name = NULL;
+
+static struct timeval tv_out, tv_in;
+
+static void nop_free_cb(void *p) {
+}
+
+static void write_cb(pa_stream *s, size_t nbytes, void *userdata) {
+ pa_lo_test_context *ctx = (pa_lo_test_context *) userdata;
+ static int ppos = 0;
+ int r, nsamp;
+
+ /* Get the real requested bytes since the last write might have been
+ * incomplete if it caused a wrap around */
+ nbytes = pa_stream_writable_size(s);
+ nsamp = nbytes / ctx->fs;
+
+ if (ppos + nsamp > N_OUT) {
+ /* Wrap-around, write to end and exit. Next iteration will fill up the
+ * rest */
+ nbytes = (N_OUT - ppos) * ctx->fs;
+ }
+
+ if (ppos == 0)
+ pa_gettimeofday(&tv_out);
+
+ r = pa_stream_write(s, &out[ppos][0], nbytes, nop_free_cb, 0, PA_SEEK_RELATIVE);
+ fail_unless(r == 0);
+
+ ppos = (ppos + nbytes / ctx->fs) % N_OUT;
+}
+
+#define WINDOW (2 * CHANNELS)
+
+static void read_cb(pa_stream *s, size_t nbytes, void *userdata) {
+ pa_lo_test_context *ctx = (pa_lo_test_context *) userdata;
+ static float last = 0.0f;
+ const float *in;
+ float cur;
+ int r;
+ unsigned int i = 0;
+ size_t l;
+
+ r = pa_stream_peek(s, (const void **)&in, &l);
+ fail_unless(r == 0);
+
+ if (l == 0)
+ return;
+
+#if 0
+ {
+ static int fd = -1;
+
+ if (fd == -1) {
+ fd = open("loopback.raw", O_CREAT | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR);
+ fail_if(fd < 0);
+ }
+
+ r = write(fd, in, l);
+ }
+#endif
+
+ do {
+#if 0
+ {
+ int j;
+ fprintf(stderr, "%g (", pa_rms(in, WINDOW));
+ for (j = 0; j < WINDOW; j++)
+ fprintf(stderr, "%g ", in[j]);
+ fprintf(stderr, ")\n");
+ }
+#endif
+ if (i + (ctx->ss * WINDOW) < l)
+ cur = pa_rms(in, WINDOW);
+ else
+ cur = pa_rms(in, (l - i) / ctx->ss);
+
+ /* We leave the definition of 0 generous since the window might
+ * straddle the 0->1 transition, raising the average power. We keep the
+ * definition of 1 tight in this case and detect the transition in the
+ * next round. */
+ if (cur - last > 0.4f) {
+ pa_gettimeofday(&tv_in);
+ fprintf(stderr, "Latency %llu\n", (unsigned long long) pa_timeval_diff(&tv_in, &tv_out));
+ }
+
+ last = cur;
+ in += WINDOW;
+ i += ctx->ss * WINDOW;
+ } while (i + (ctx->ss * WINDOW) <= l);
+
+ pa_stream_drop(s);
+}
+
+START_TEST (loopback_test) {
+ int i, pulse_hz = SAMPLE_HZ / 1000;
+
+ test_ctx.context_name = context_name;
+
+ test_ctx.sample_spec.format = PA_SAMPLE_FLOAT32,
+ test_ctx.sample_spec.rate = SAMPLE_HZ,
+ test_ctx.sample_spec.channels = CHANNELS,
+
+ test_ctx.play_latency = 25;
+ test_ctx.rec_latency = 5;
+
+ test_ctx.read_cb = read_cb;
+ test_ctx.write_cb = write_cb;
+
+ /* Generate a square pulse */
+ for (i = 0; i < N_OUT; i++)
+ if (i < pulse_hz)
+ out[i][0] = out[i][1] = 1.0f;
+ else
+ out[i][0] = out[i][1] = 0.0f;
+
+ fail_unless(pa_lo_test_init(&test_ctx) == 0);
+ fail_unless(pa_lo_test_run(&test_ctx) == 0);
+ pa_lo_test_deinit(&test_ctx);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ context_name = argv[0];
+
+ s = suite_create("Loopback latency");
+ tc = tcase_create("loopback latency");
+ tcase_add_test(tc, loopback_test);
+ tcase_set_timeout(tc, 5 * 60);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_set_fork_status(sr, CK_NOFORK);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/lo-test-util.c b/src/tests/lo-test-util.c
new file mode 100644
index 0000000..40002a2
--- /dev/null
+++ b/src/tests/lo-test-util.c
@@ -0,0 +1,324 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2013 Collabora Ltd.
+ Author: Arun Raghavan <arun.raghavan@collabora.co.uk>
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <math.h>
+
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
+
+#include "lo-test-util.h"
+
+/* Keep the frequency high so RMS over ranges of a few ms remains relatively
+ * high as well */
+#define TONE_HZ 4410
+
+static void nop_free_cb(void *p) {
+}
+
+static void underflow_cb(struct pa_stream *s, void *userdata) {
+ pa_log_warn("Underflow");
+}
+
+static void overflow_cb(struct pa_stream *s, void *userdata) {
+ pa_log_warn("Overlow");
+}
+
+/*
+ * We run a simple volume calibration so that we know we can detect the signal
+ * being played back. We start with the playback stream at 100% volume, and
+ * capture at 0.
+ *
+ * First, we then play a sine wave and increase the capture volume till the
+ * signal is clearly received.
+ *
+ * Next, we play back silence and make sure that the level is low enough to
+ * distinguish from when playback is happening.
+ *
+ * Finally, we hand off to the real read/write callbacks to run the actual
+ * test.
+ */
+
+enum {
+ CALIBRATION_ONE,
+ CALIBRATION_ZERO,
+ CALIBRATION_DONE,
+};
+
+static int cal_state = CALIBRATION_ONE;
+
+static void calibrate_write_cb(pa_stream *s, size_t nbytes, void *userdata) {
+ pa_lo_test_context *ctx = (pa_lo_test_context *) userdata;
+ int i, nsamp = nbytes / ctx->fs;
+ float tmp[nsamp][2];
+ static int count = 0;
+
+ /* Write out a sine tone */
+ for (i = 0; i < nsamp; i++)
+ tmp[i][0] = tmp[i][1] = cal_state == CALIBRATION_ONE ? sinf(count++ * TONE_HZ * 2 * M_PI / ctx->sample_spec.rate) : 0.0f;
+
+ pa_assert_se(pa_stream_write(s, &tmp, nbytes, nop_free_cb, 0, PA_SEEK_RELATIVE) == 0);
+
+ if (cal_state == CALIBRATION_DONE)
+ pa_stream_set_write_callback(s, ctx->write_cb, ctx);
+}
+
+static void calibrate_read_cb(pa_stream *s, size_t nbytes, void *userdata) {
+ pa_lo_test_context *ctx = (pa_lo_test_context *) userdata;
+ static double v = 0;
+ static int skip = 0, confirm;
+
+ pa_cvolume vol;
+ pa_operation *o;
+ int nsamp;
+ float *in;
+ size_t l;
+
+ pa_assert_se(pa_stream_peek(s, (const void **)&in, &l) == 0);
+
+ nsamp = l / ctx->fs;
+
+ /* For each state or volume step change, throw out a few samples so we know
+ * we're seeing the changed samples. */
+ if (skip++ < 100)
+ goto out;
+ else
+ skip = 0;
+
+ switch (cal_state) {
+ case CALIBRATION_ONE:
+ /* Try to detect the sine wave. RMS is 0.5, */
+ if (pa_rms(in, nsamp) < 0.40f) {
+ confirm = 0;
+ v += 0.02f;
+
+ if (v > 1.0) {
+ pa_log_error("Capture signal too weak at 100%% volume (%g). Giving up.", pa_rms(in, nsamp));
+ pa_assert_not_reached();
+ }
+
+ pa_cvolume_set(&vol, ctx->sample_spec.channels, v * PA_VOLUME_NORM);
+ o = pa_context_set_source_output_volume(ctx->context, pa_stream_get_index(s), &vol, NULL, NULL);
+ pa_assert(o != NULL);
+ pa_operation_unref(o);
+ } else {
+ /* Make sure the signal strength is steadily above our threshold */
+ if (++confirm > 5) {
+#if 0
+ pa_log_debug(stderr, "Capture volume = %g (%g)", v, pa_rms(in, nsamp));
+#endif
+ cal_state = CALIBRATION_ZERO;
+ }
+ }
+
+ break;
+
+ case CALIBRATION_ZERO:
+ /* Now make sure silence doesn't trigger a false positive because
+ * of noise. */
+ if (pa_rms(in, nsamp) > 0.1f) {
+ pa_log_warn("Too much noise on capture (%g). Giving up.", pa_rms(in, nsamp));
+ pa_assert_not_reached();
+ }
+
+ cal_state = CALIBRATION_DONE;
+ pa_stream_set_read_callback(s, ctx->read_cb, ctx);
+
+ break;
+
+ default:
+ break;
+ }
+
+out:
+ pa_stream_drop(s);
+}
+
+/* This routine is called whenever the stream state changes */
+static void stream_state_callback(pa_stream *s, void *userdata) {
+ pa_lo_test_context *ctx = (pa_lo_test_context *) userdata;
+
+ switch (pa_stream_get_state(s)) {
+ case PA_STREAM_UNCONNECTED:
+ case PA_STREAM_CREATING:
+ case PA_STREAM_TERMINATED:
+ break;
+
+ case PA_STREAM_READY: {
+ pa_cvolume vol;
+ pa_operation *o;
+
+ /* Set volumes for calibration */
+ if (s == ctx->play_stream) {
+ pa_cvolume_set(&vol, ctx->sample_spec.channels, PA_VOLUME_NORM);
+ o = pa_context_set_sink_input_volume(ctx->context, pa_stream_get_index(s), &vol, NULL, NULL);
+ } else {
+ pa_cvolume_set(&vol, ctx->sample_spec.channels, pa_sw_volume_from_linear(0.0));
+ o = pa_context_set_source_output_volume(ctx->context, pa_stream_get_index(s), &vol, NULL, NULL);
+ }
+
+ if (!o) {
+ pa_log_error("Could not set stream volume: %s", pa_strerror(pa_context_errno(ctx->context)));
+ pa_assert_not_reached();
+ } else
+ pa_operation_unref(o);
+
+ break;
+ }
+
+ case PA_STREAM_FAILED:
+ default:
+ pa_log_error("Stream error: %s", pa_strerror(pa_context_errno(ctx->context)));
+ pa_assert_not_reached();
+ }
+}
+
+/* This is called whenever the context status changes */
+static void context_state_callback(pa_context *c, void *userdata) {
+ pa_lo_test_context *ctx = (pa_lo_test_context *) userdata;
+ pa_mainloop_api *api;
+
+ switch (pa_context_get_state(c)) {
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+
+ case PA_CONTEXT_READY: {
+ pa_buffer_attr buffer_attr;
+
+ pa_thread_make_realtime(4);
+
+ /* Create playback stream */
+ buffer_attr.maxlength = -1;
+ buffer_attr.tlength = ctx->sample_spec.rate * ctx->fs * ctx->play_latency / 1000;
+ buffer_attr.prebuf = 0; /* Setting prebuf to 0 guarantees us the stream will run synchronously, no matter what */
+ buffer_attr.minreq = -1;
+ buffer_attr.fragsize = -1;
+
+ ctx->play_stream = pa_stream_new(c, "loopback: play", &ctx->sample_spec, NULL);
+ pa_assert(ctx->play_stream != NULL);
+ pa_stream_set_state_callback(ctx->play_stream, stream_state_callback, ctx);
+ pa_stream_set_write_callback(ctx->play_stream, calibrate_write_cb, ctx);
+ pa_stream_set_underflow_callback(ctx->play_stream, underflow_cb, userdata);
+
+ pa_stream_connect_playback(ctx->play_stream, getenv("TEST_SINK"), &buffer_attr,
+ PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL);
+
+ /* Create capture stream */
+ buffer_attr.maxlength = -1;
+ buffer_attr.tlength = (uint32_t) -1;
+ buffer_attr.prebuf = 0;
+ buffer_attr.minreq = (uint32_t) -1;
+ buffer_attr.fragsize = ctx->sample_spec.rate * ctx->fs * ctx->rec_latency / 1000;
+
+ ctx->rec_stream = pa_stream_new(c, "loopback: rec", &ctx->sample_spec, NULL);
+ pa_assert(ctx->rec_stream != NULL);
+ pa_stream_set_state_callback(ctx->rec_stream, stream_state_callback, ctx);
+ pa_stream_set_read_callback(ctx->rec_stream, calibrate_read_cb, ctx);
+ pa_stream_set_overflow_callback(ctx->rec_stream, overflow_cb, userdata);
+
+ pa_stream_connect_record(ctx->rec_stream, getenv("TEST_SOURCE"), &buffer_attr,
+ PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE);
+
+ break;
+ }
+
+ case PA_CONTEXT_TERMINATED:
+ api = pa_mainloop_get_api(ctx->mainloop);
+ api->quit(api, 0);
+ break;
+
+ case PA_CONTEXT_FAILED:
+ default:
+ pa_log_error("Context error: %s", pa_strerror(pa_context_errno(c)));
+ pa_assert_not_reached();
+ }
+}
+
+int pa_lo_test_init(pa_lo_test_context *ctx) {
+ /* FIXME: need to deal with non-float samples at some point */
+ pa_assert(ctx->sample_spec.format == PA_SAMPLE_FLOAT32);
+
+ ctx->ss = pa_sample_size(&ctx->sample_spec);
+ ctx->fs = pa_frame_size(&ctx->sample_spec);
+
+ ctx->mainloop = pa_mainloop_new();
+ ctx->context = pa_context_new(pa_mainloop_get_api(ctx->mainloop), ctx->context_name);
+
+ pa_context_set_state_callback(ctx->context, context_state_callback, ctx);
+
+ /* Connect the context */
+ if (pa_context_connect(ctx->context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) {
+ pa_log_error("pa_context_connect() failed.");
+ goto quit;
+ }
+
+ return 0;
+
+quit:
+ pa_context_unref(ctx->context);
+ pa_mainloop_free(ctx->mainloop);
+
+ return -1;
+}
+
+int pa_lo_test_run(pa_lo_test_context *ctx) {
+ int ret;
+
+ if (pa_mainloop_run(ctx->mainloop, &ret) < 0) {
+ pa_log_error("pa_mainloop_run() failed.");
+ return -1;
+ }
+
+ return 0;
+}
+
+void pa_lo_test_deinit(pa_lo_test_context *ctx) {
+ if (ctx->play_stream) {
+ pa_stream_disconnect(ctx->play_stream);
+ pa_stream_unref(ctx->play_stream);
+ }
+
+ if (ctx->rec_stream) {
+ pa_stream_disconnect(ctx->rec_stream);
+ pa_stream_unref(ctx->rec_stream);
+ }
+
+ if (ctx->context)
+ pa_context_unref(ctx->context);
+
+ if (ctx->mainloop)
+ pa_mainloop_free(ctx->mainloop);
+}
+
+float pa_rms(const float *s, int n) {
+ float sq = 0;
+ int i;
+
+ for (i = 0; i < n; i++)
+ sq += s[i] * s[i];
+
+ return sqrtf(sq / n);
+}
diff --git a/src/tests/lo-test-util.h b/src/tests/lo-test-util.h
new file mode 100644
index 0000000..a6b0cde
--- /dev/null
+++ b/src/tests/lo-test-util.h
@@ -0,0 +1,55 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2013 Collabora Ltd.
+ Author: Arun Raghavan <arun.raghavan@collabora.co.uk>
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/pulseaudio.h>
+
+typedef struct pa_lo_test_context {
+ /* Tests need to set these */
+ const char *context_name;
+
+ pa_sample_spec sample_spec;
+ int play_latency; /* ms */
+ int rec_latency; /* ms */
+
+ pa_stream_request_cb_t write_cb, read_cb;
+
+ /* These are set by lo_test_init() */
+ pa_mainloop *mainloop;
+ pa_context *context;
+
+ pa_stream *play_stream, *rec_stream;
+
+ int ss, fs; /* sample size, frame size for convenience */
+} pa_lo_test_context;
+
+/* Initialise the test parameters, connect */
+int pa_lo_test_init(pa_lo_test_context *ctx);
+/* Start running the test */
+int pa_lo_test_run(pa_lo_test_context *ctx);
+/* Clean up */
+void pa_lo_test_deinit(pa_lo_test_context *ctx);
+
+/* Return RMS for the given signal. Assumes the data is a single channel for
+ * simplicity */
+float pa_rms(const float *s, int n);
diff --git a/src/tests/lock-autospawn-test.c b/src/tests/lock-autospawn-test.c
new file mode 100644
index 0000000..d475d2d
--- /dev/null
+++ b/src/tests/lock-autospawn-test.c
@@ -0,0 +1,129 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2008 Lennart Poettering
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <check.h>
+
+#include <string.h>
+
+#include <pulsecore/poll.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/lock-autospawn.h>
+#include <pulse/util.h>
+
+static void thread_func(void*k) {
+ fail_unless(pa_autospawn_lock_init() >= 0);
+
+ pa_log("%i, Trying to acquire lock.", PA_PTR_TO_INT(k));
+
+ fail_unless(pa_autospawn_lock_acquire(true) > 0);
+
+ pa_log("%i, Got the lock!, Sleeping for 5s", PA_PTR_TO_INT(k));
+
+ pa_msleep(5000);
+
+ pa_log("%i, Releasing", PA_PTR_TO_INT(k));
+
+ pa_autospawn_lock_release();
+
+ pa_autospawn_lock_done(false);
+}
+
+static void thread_func2(void *k) {
+ int fd;
+
+ fail_unless((fd = pa_autospawn_lock_init()) >= 0);
+
+ pa_log("%i, Trying to acquire lock.", PA_PTR_TO_INT(k));
+
+ for (;;) {
+ struct pollfd pollfd;
+ int j;
+
+ if ((j = pa_autospawn_lock_acquire(false)) > 0)
+ break;
+
+ fail_unless(j == 0);
+
+ memset(&pollfd, 0, sizeof(pollfd));
+ pollfd.fd = fd;
+ pollfd.events = POLLIN;
+
+ fail_unless(pa_poll(&pollfd, 1, -1) == 1);
+
+ pa_log("%i, woke up", PA_PTR_TO_INT(k));
+ }
+
+ pa_log("%i, Got the lock!, Sleeping for 5s", PA_PTR_TO_INT(k));
+
+ pa_msleep(5000);
+
+ pa_log("%i, Releasing", PA_PTR_TO_INT(k));
+
+ pa_autospawn_lock_release();
+
+ pa_autospawn_lock_done(false);
+}
+
+START_TEST (lockautospawn_test) {
+ pa_thread *a, *b, *c, *d;
+
+ pa_assert_se((a = pa_thread_new("test1", thread_func, PA_INT_TO_PTR(1))));
+ pa_assert_se((b = pa_thread_new("test2", thread_func2, PA_INT_TO_PTR(2))));
+ pa_assert_se((c = pa_thread_new("test3", thread_func2, PA_INT_TO_PTR(3))));
+ pa_assert_se((d = pa_thread_new("test4", thread_func, PA_INT_TO_PTR(4))));
+
+ pa_thread_join(a);
+ pa_thread_join(b);
+ pa_thread_join(c);
+ pa_thread_join(d);
+
+ pa_thread_free(a);
+ pa_thread_free(b);
+ pa_thread_free(c);
+ pa_thread_free(d);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ s = suite_create("Lock Auto Spawn");
+ tc = tcase_create("lockautospawn");
+ tcase_add_test(tc, lockautospawn_test);
+ /* the default timeout is too small,
+ * set it to a reasonable large one.
+ */
+ tcase_set_timeout(tc, 60 * 60);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/mainloop-test.c b/src/tests/mainloop-test.c
new file mode 100644
index 0000000..3a3e9bb
--- /dev/null
+++ b/src/tests/mainloop-test.c
@@ -0,0 +1,140 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <assert.h>
+#include <check.h>
+
+#include <pulse/rtclock.h>
+#include <pulse/timeval.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/core-rtclock.h>
+
+#ifdef GLIB_MAIN_LOOP
+
+#include <glib.h>
+#include <pulse/glib-mainloop.h>
+
+static GMainLoop* glib_main_loop = NULL;
+
+#else /* GLIB_MAIN_LOOP */
+#include <pulse/mainloop.h>
+#endif /* GLIB_MAIN_LOOP */
+
+static pa_defer_event *de;
+
+static void iocb(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) {
+ unsigned char c;
+ pa_assert_se(read(fd, &c, sizeof(c)) >= 0);
+ fprintf(stderr, "IO EVENT: %c\n", c < 32 ? '.' : c);
+ a->defer_enable(de, 1);
+}
+
+static void dcb(pa_mainloop_api*a, pa_defer_event *e, void *userdata) {
+ fprintf(stderr, "DEFER EVENT\n");
+ a->defer_enable(e, 0);
+}
+
+static void tcb(pa_mainloop_api*a, pa_time_event *e, const struct timeval *tv, void *userdata) {
+ fprintf(stderr, "TIME EVENT\n");
+
+#if defined(GLIB_MAIN_LOOP)
+ g_main_loop_quit(glib_main_loop);
+#else
+ a->quit(a, 0);
+#endif
+}
+
+START_TEST (mainloop_test) {
+ pa_mainloop_api *a;
+ pa_io_event *ioe;
+ pa_time_event *te;
+ struct timeval tv;
+
+#ifdef GLIB_MAIN_LOOP
+ pa_glib_mainloop *g;
+
+ glib_main_loop = g_main_loop_new(NULL, FALSE);
+ fail_if(!glib_main_loop);
+
+ g = pa_glib_mainloop_new(NULL);
+ fail_if(!g);
+
+ a = pa_glib_mainloop_get_api(g);
+ fail_if(!a);
+#else /* GLIB_MAIN_LOOP */
+ pa_mainloop *m;
+
+ m = pa_mainloop_new();
+ fail_if(!m);
+
+ a = pa_mainloop_get_api(m);
+ fail_if(!a);
+#endif /* GLIB_MAIN_LOOP */
+
+ ioe = a->io_new(a, 0, PA_IO_EVENT_INPUT, iocb, NULL);
+ fail_if(!ioe);
+
+ de = a->defer_new(a, dcb, NULL);
+ fail_if(!de);
+
+ te = a->time_new(a, pa_timeval_rtstore(&tv, pa_rtclock_now() + 2 * PA_USEC_PER_SEC, true), tcb, NULL);
+
+#if defined(GLIB_MAIN_LOOP)
+ g_main_loop_run(glib_main_loop);
+#else
+ pa_mainloop_run(m, NULL);
+#endif
+
+ a->time_free(te);
+ a->defer_free(de);
+ a->io_free(ioe);
+
+#ifdef GLIB_MAIN_LOOP
+ pa_glib_mainloop_free(g);
+ g_main_loop_unref(glib_main_loop);
+#else
+ pa_mainloop_free(m);
+#endif
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ s = suite_create("MainLoop");
+ tc = tcase_create("mainloop");
+ tcase_add_test(tc, mainloop_test);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/mcalign-test.c b/src/tests/mcalign-test.c
new file mode 100644
index 0000000..3127ccf
--- /dev/null
+++ b/src/tests/mcalign-test.c
@@ -0,0 +1,106 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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 PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/mcalign.h>
+
+/* A simple program for testing pa_mcalign */
+
+int main(int argc, char *argv[]) {
+ pa_mempool *p;
+ pa_mcalign *a;
+ pa_memchunk c;
+
+ p = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true);
+
+ a = pa_mcalign_new(11);
+
+ pa_memchunk_reset(&c);
+
+ srand((unsigned) time(NULL));
+
+ for (;;) {
+ ssize_t r;
+ size_t l;
+
+ if (!c.memblock) {
+ c.memblock = pa_memblock_new(p, 2048);
+ c.index = c.length = 0;
+ }
+
+ pa_assert(c.index < pa_memblock_get_length(c.memblock));
+
+ l = pa_memblock_get_length(c.memblock) - c.index;
+
+ l = l <= 1 ? l : (size_t) rand() % (l-1) +1;
+
+ p = pa_memblock_acquire(c.memblock);
+
+ if ((r = read(STDIN_FILENO, (uint8_t*) p + c.index, l)) <= 0) {
+ pa_memblock_release(c.memblock);
+ fprintf(stderr, "read() failed: %s\n", r < 0 ? strerror(errno) : "EOF");
+ break;
+ }
+
+ pa_memblock_release(c.memblock);
+
+ c.length = (size_t) r;
+ pa_mcalign_push(a, &c);
+ fprintf(stderr, "Read %zd bytes\n", r);
+
+ c.index += (size_t) r;
+
+ if (c.index >= pa_memblock_get_length(c.memblock)) {
+ pa_memblock_unref(c.memblock);
+ pa_memchunk_reset(&c);
+ }
+
+ for (;;) {
+ pa_memchunk t;
+
+ if (pa_mcalign_pop(a, &t) < 0)
+ break;
+
+ p = pa_memblock_acquire(t.memblock);
+ pa_loop_write(STDOUT_FILENO, (uint8_t*) p + t.index, t.length, NULL);
+ pa_memblock_release(t.memblock);
+ fprintf(stderr, "Wrote %lu bytes.\n", (unsigned long) t.length);
+
+ pa_memblock_unref(t.memblock);
+ }
+ }
+
+ pa_mcalign_free(a);
+
+ if (c.memblock)
+ pa_memblock_unref(c.memblock);
+
+ pa_mempool_unref(p);
+
+ return 0;
+}
diff --git a/src/tests/memblock-test.c b/src/tests/memblock-test.c
new file mode 100644
index 0000000..d48349f
--- /dev/null
+++ b/src/tests/memblock-test.c
@@ -0,0 +1,199 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include <check.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/log.h>
+#include <pulsecore/memblock.h>
+#include <pulsecore/macro.h>
+
+static void release_cb(pa_memimport *i, uint32_t block_id, void *userdata) {
+ pa_log("%s: Imported block %u is released.", (char*) userdata, block_id);
+}
+
+static void revoke_cb(pa_memexport *e, uint32_t block_id, void *userdata) {
+ pa_log("%s: Exported block %u is revoked.", (char*) userdata, block_id);
+}
+
+static void print_stats(pa_mempool *p, const char *text) {
+ const pa_mempool_stat*s = pa_mempool_get_stat(p);
+
+ pa_log_debug("%s = {\n"
+ "\tn_allocated = %u\n"
+ "\tn_accumulated = %u\n"
+ "\tn_imported = %u\n"
+ "\tn_exported = %u\n"
+ "\tallocated_size = %u\n"
+ "\taccumulated_size = %u\n"
+ "\timported_size = %u\n"
+ "\texported_size = %u\n"
+ "\tn_too_large_for_pool = %u\n"
+ "\tn_pool_full = %u\n"
+ "}",
+ text,
+ (unsigned) pa_atomic_load(&s->n_allocated),
+ (unsigned) pa_atomic_load(&s->n_accumulated),
+ (unsigned) pa_atomic_load(&s->n_imported),
+ (unsigned) pa_atomic_load(&s->n_exported),
+ (unsigned) pa_atomic_load(&s->allocated_size),
+ (unsigned) pa_atomic_load(&s->accumulated_size),
+ (unsigned) pa_atomic_load(&s->imported_size),
+ (unsigned) pa_atomic_load(&s->exported_size),
+ (unsigned) pa_atomic_load(&s->n_too_large_for_pool),
+ (unsigned) pa_atomic_load(&s->n_pool_full));
+}
+
+START_TEST (memblock_test) {
+ pa_mempool *pool_a, *pool_b, *pool_c;
+ unsigned id_a, id_b, id_c;
+ pa_memexport *export_a, *export_b;
+ pa_memimport *import_b, *import_c;
+ pa_memblock *mb_a, *mb_b, *mb_c;
+ int r, i;
+ pa_memblock* blocks[5];
+ pa_mem_type_t mem_type;
+ uint32_t id, shm_id;
+ size_t offset, size;
+ char *x;
+
+ const char txt[] = "This is a test!";
+
+ pool_a = pa_mempool_new(PA_MEM_TYPE_SHARED_POSIX, 0, true);
+ fail_unless(pool_a != NULL);
+ pool_b = pa_mempool_new(PA_MEM_TYPE_SHARED_POSIX, 0, true);
+ fail_unless(pool_b != NULL);
+ pool_c = pa_mempool_new(PA_MEM_TYPE_SHARED_POSIX, 0, true);
+ fail_unless(pool_c != NULL);
+
+ pa_mempool_get_shm_id(pool_a, &id_a);
+ pa_mempool_get_shm_id(pool_b, &id_b);
+ pa_mempool_get_shm_id(pool_c, &id_c);
+
+ blocks[0] = pa_memblock_new_fixed(pool_a, (void*) txt, sizeof(txt), 1);
+
+ blocks[1] = pa_memblock_new(pool_a, sizeof(txt));
+ x = pa_memblock_acquire(blocks[1]);
+ snprintf(x, pa_memblock_get_length(blocks[1]), "%s", txt);
+ pa_memblock_release(blocks[1]);
+
+ blocks[2] = pa_memblock_new_pool(pool_a, sizeof(txt));
+ x = pa_memblock_acquire(blocks[2]);
+ snprintf(x, pa_memblock_get_length(blocks[2]), "%s", txt);
+ pa_memblock_release(blocks[2]);
+
+ blocks[3] = pa_memblock_new_malloced(pool_a, pa_xstrdup(txt), sizeof(txt));
+ blocks[4] = NULL;
+
+ for (i = 0; blocks[i]; i++) {
+ pa_log("Memory block %u", i);
+
+ mb_a = blocks[i];
+ fail_unless(mb_a != NULL);
+
+ export_a = pa_memexport_new(pool_a, revoke_cb, (void*) "A");
+ fail_unless(export_a != NULL);
+ export_b = pa_memexport_new(pool_b, revoke_cb, (void*) "B");
+ fail_unless(export_b != NULL);
+
+ import_b = pa_memimport_new(pool_b, release_cb, (void*) "B");
+ fail_unless(import_b != NULL);
+ import_c = pa_memimport_new(pool_c, release_cb, (void*) "C");
+ fail_unless(import_b != NULL);
+
+ r = pa_memexport_put(export_a, mb_a, &mem_type, &id, &shm_id, &offset, &size);
+ fail_unless(r >= 0);
+ fail_unless(shm_id == id_a);
+
+ pa_log("A: Memory block exported as %u", id);
+
+ mb_b = pa_memimport_get(import_b, PA_MEM_TYPE_SHARED_POSIX, id, shm_id, offset, size, false);
+ fail_unless(mb_b != NULL);
+ r = pa_memexport_put(export_b, mb_b, &mem_type, &id, &shm_id, &offset, &size);
+ fail_unless(r >= 0);
+ fail_unless(shm_id == id_a || shm_id == id_b);
+ pa_memblock_unref(mb_b);
+
+ pa_log("B: Memory block exported as %u", id);
+
+ mb_c = pa_memimport_get(import_c, PA_MEM_TYPE_SHARED_POSIX, id, shm_id, offset, size, false);
+ fail_unless(mb_c != NULL);
+ x = pa_memblock_acquire(mb_c);
+ pa_log_debug("1 data=%s", x);
+ pa_memblock_release(mb_c);
+
+ print_stats(pool_a, "A");
+ print_stats(pool_b, "B");
+ print_stats(pool_c, "C");
+
+ pa_memexport_free(export_b);
+ x = pa_memblock_acquire(mb_c);
+ pa_log_debug("2 data=%s", x);
+ pa_memblock_release(mb_c);
+ pa_memblock_unref(mb_c);
+
+ pa_memimport_free(import_b);
+
+ pa_memblock_unref(mb_a);
+
+ pa_memimport_free(import_c);
+ pa_memexport_free(export_a);
+ }
+
+ pa_log("vacuuming...");
+
+ pa_mempool_vacuum(pool_a);
+ pa_mempool_vacuum(pool_b);
+ pa_mempool_vacuum(pool_c);
+
+ pa_log("vacuuming done...");
+
+ pa_mempool_unref(pool_a);
+ pa_mempool_unref(pool_b);
+ pa_mempool_unref(pool_c);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ if (!getenv("MAKE_CHECK"))
+ pa_log_set_level(PA_LOG_DEBUG);
+
+ s = suite_create("Memblock");
+ tc = tcase_create("memblock");
+ tcase_add_test(tc, memblock_test);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/memblockq-test.c b/src/tests/memblockq-test.c
new file mode 100644
index 0000000..2a9b88a
--- /dev/null
+++ b/src/tests/memblockq-test.c
@@ -0,0 +1,661 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <signal.h>
+
+#include <check.h>
+
+#include <pulsecore/memblockq.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/strbuf.h>
+#include <pulsecore/core-util.h>
+
+#include <pulse/xmalloc.h>
+
+static const char *fixed[] = {
+ "1122444411441144__22__11______3333______________________________",
+ "__________________3333__________________________________________"
+};
+static const char *manual[] = {
+ "1122444411441144__22__11______3333______________________________",
+ "__________________3333______________________________"
+};
+
+/*
+ * utility function to create a memchunk
+ */
+static pa_memchunk memchunk_from_str(pa_mempool *p, const char* data)
+{
+ pa_memchunk res;
+ size_t size = strlen(data);
+
+ res.memblock = pa_memblock_new_fixed(p, (void*)data, size, true);
+ ck_assert_ptr_ne(res.memblock, NULL);
+
+ res.index = 0;
+ res.length = pa_memblock_get_length(res.memblock);
+
+ return res;
+}
+
+static void dump_chunk(const pa_memchunk *chunk, pa_strbuf *buf) {
+ size_t n;
+ void *q;
+ char *e;
+
+ fail_unless(chunk != NULL);
+
+ q = pa_memblock_acquire(chunk->memblock);
+ for (e = (char*) q + chunk->index, n = 0; n < chunk->length; n++, e++) {
+ fprintf(stderr, "%c", *e);
+ pa_strbuf_putc(buf, *e);
+ }
+ pa_memblock_release(chunk->memblock);
+}
+
+static void dump(pa_memblockq *bq, int n) {
+ pa_memchunk out;
+ pa_strbuf *buf;
+ char *str;
+
+ pa_assert(bq);
+
+ /* First let's dump this as fixed block */
+ fprintf(stderr, "FIXED >");
+ pa_memblockq_peek_fixed_size(bq, 64, &out);
+ buf = pa_strbuf_new();
+ dump_chunk(&out, buf);
+ pa_memblock_unref(out.memblock);
+ str = pa_strbuf_to_string_free(buf);
+ fail_unless(pa_streq(str, fixed[n]));
+ pa_xfree(str);
+ fprintf(stderr, "<\n");
+
+ /* Then let's dump the queue manually */
+ fprintf(stderr, "MANUAL>");
+
+ buf = pa_strbuf_new();
+ for (;;) {
+ if (pa_memblockq_peek(bq, &out) < 0)
+ break;
+
+ dump_chunk(&out, buf);
+ pa_memblock_unref(out.memblock);
+ pa_memblockq_drop(bq, out.length);
+ }
+ str = pa_strbuf_to_string_free(buf);
+ fail_unless(pa_streq(str, manual[n]));
+ pa_xfree(str);
+ fprintf(stderr, "<\n");
+}
+
+/*
+ * utility function to validate invariants
+ *
+ * The different values like base, maxlength etc follow certain rules.
+ * This convenience function makes sure that changes don't violate
+ * these rules.
+ */
+static void check_queue_invariants(pa_memblockq *bq) {
+ size_t base = pa_memblockq_get_base(bq);
+ size_t maxlength = pa_memblockq_get_maxlength(bq);
+ size_t tlength = pa_memblockq_get_tlength(bq);
+ size_t minreq = pa_memblockq_get_minreq(bq);
+ size_t prebuf = pa_memblockq_get_prebuf(bq);
+ size_t length = pa_memblockq_get_length(bq);
+
+ /* base > zero */
+ ck_assert_int_gt(base, 0);
+
+ /* maxlength multiple of base
+ * maxlength >= base */
+ ck_assert_int_eq(maxlength % base, 0);
+ ck_assert_int_ge(maxlength, base);
+
+ /* tlength multiple of base
+ * tlength >= base
+ * tlength <= maxlength */
+ ck_assert_int_eq(tlength % base, 0);
+ ck_assert_int_ge(tlength, base);
+ ck_assert_int_le(tlength, maxlength);
+
+ /* minreq multiple of base
+ * minreq >= base
+ * minreq <= tlength */
+ ck_assert_int_eq(minreq % base, 0);
+ ck_assert_int_ge(minreq, base);
+ ck_assert_int_le(minreq, tlength);
+
+ /* prebuf multiple of base
+ * prebuf >= 0
+ * prebuf <= tlength + base - minreq
+ * prebuf <= tlength (because minreq >= base) */
+ ck_assert_int_eq(prebuf % base, 0);
+ ck_assert_int_ge(prebuf, 0);
+ ck_assert_int_le(prebuf, tlength + base - minreq);
+ ck_assert_int_le(prebuf, tlength);
+
+ /* length >= 0
+ * length <= maxlength */
+ ck_assert_int_ge(length, 0);
+ ck_assert_int_le(length, maxlength);
+}
+
+START_TEST (memchunk_from_str_test) {
+ pa_mempool *p;
+ pa_memchunk chunk;
+
+ p = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true);
+ ck_assert_ptr_ne(p, NULL);
+
+ /* allocate memchunk and check default settings */
+ chunk = memchunk_from_str(p, "abcd");
+ ck_assert_ptr_ne(chunk.memblock, NULL);
+ ck_assert_int_eq(chunk.index, 0);
+ ck_assert_int_eq(chunk.length, 4);
+
+ /* cleanup */
+ pa_memblock_unref(chunk.memblock);
+ pa_mempool_unref(p);
+}
+END_TEST
+
+START_TEST (memblockq_test_initial_properties) {
+ pa_mempool *p;
+ pa_memblockq *bq;
+ pa_memchunk silence;
+ pa_sample_spec ss = {
+ .format = PA_SAMPLE_S32BE,
+ .rate = 48000,
+ .channels = 1
+ };
+ int64_t idx = 0;
+ size_t maxlength = 100;
+ size_t tlength = 20;
+ size_t prebuf = 16;
+ size_t minreq = 8;
+ size_t maxrewind = 40;
+
+ p = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true);
+ ck_assert_ptr_ne(p, NULL);
+
+ silence = memchunk_from_str(p, "__");
+
+ bq = pa_memblockq_new("test memblockq", idx, maxlength, tlength, &ss, prebuf, minreq, maxrewind, &silence);
+ fail_unless(bq != NULL);
+
+ /* check initial properties */
+ ck_assert_int_eq(pa_memblockq_is_readable(bq), false);
+ ck_assert_int_eq(pa_memblockq_get_length(bq), 0);
+ ck_assert_int_eq(pa_memblockq_get_maxlength(bq), maxlength);
+ ck_assert_int_eq(pa_memblockq_get_tlength(bq), tlength);
+ ck_assert_int_eq(pa_memblockq_get_prebuf(bq), prebuf);
+ ck_assert_int_eq(pa_memblockq_get_minreq(bq), minreq);
+ ck_assert_int_eq(pa_memblockq_get_maxrewind(bq), maxrewind);
+ ck_assert_int_eq(pa_memblockq_get_base(bq), pa_frame_size(&ss));
+ ck_assert_int_eq(pa_memblockq_get_read_index(bq), 0);
+ ck_assert_int_eq(pa_memblockq_get_write_index(bq), 0);
+
+ check_queue_invariants(bq);
+
+ /* Check reporting of missing bytes:
+ * Initially, tlength bytes are missing. The second call doesn't
+ * report additional missing data since the first call. */
+ ck_assert_int_eq(pa_memblockq_pop_missing(bq), tlength);
+ ck_assert_int_eq(pa_memblockq_pop_missing(bq), 0);
+
+ /* cleanup */
+ pa_memblockq_free(bq);
+ pa_memblock_unref(silence.memblock);
+ pa_mempool_unref(p);
+}
+END_TEST
+
+START_TEST (memblockq_test) {
+ int ret;
+
+ pa_mempool *p;
+ pa_memblockq *bq;
+ pa_memchunk chunk1, chunk2, chunk3, chunk4;
+ pa_memchunk silence;
+ pa_sample_spec ss = {
+ .format = PA_SAMPLE_S16LE,
+ .rate = 48000,
+ .channels = 1
+ };
+
+ p = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true);
+ ck_assert_ptr_ne(p, NULL);
+
+ silence = memchunk_from_str(p, "__");
+
+ bq = pa_memblockq_new("test memblockq", 0, 200, 10, &ss, 4, 4, 40, &silence);
+ fail_unless(bq != NULL);
+ check_queue_invariants(bq);
+
+ chunk1 = memchunk_from_str(p, "11");
+ chunk2 = memchunk_from_str(p, "XX22");
+ chunk2.index += 2;
+ chunk2.length -= 2;
+ chunk3 = memchunk_from_str(p, "3333");
+ chunk4 = memchunk_from_str(p, "44444444");
+
+ ret = pa_memblockq_push(bq, &chunk1);
+ fail_unless(ret == 0);
+
+ ret = pa_memblockq_push(bq, &chunk2);
+ fail_unless(ret == 0);
+
+ ret = pa_memblockq_push(bq, &chunk3);
+ fail_unless(ret == 0);
+
+ ret = pa_memblockq_push(bq, &chunk4);
+ fail_unless(ret == 0);
+
+ check_queue_invariants(bq);
+
+ pa_memblockq_seek(bq, -6, 0, true);
+ ret = pa_memblockq_push(bq, &chunk3);
+ fail_unless(ret == 0);
+
+ pa_memblockq_seek(bq, -2, 0, true);
+ ret = pa_memblockq_push(bq, &chunk1);
+ fail_unless(ret == 0);
+
+ pa_memblockq_seek(bq, -10, 0, true);
+ ret = pa_memblockq_push(bq, &chunk4);
+ fail_unless(ret == 0);
+
+ pa_memblockq_seek(bq, 10, 0, true);
+
+ ret = pa_memblockq_push(bq, &chunk1);
+ fail_unless(ret == 0);
+
+ pa_memblockq_seek(bq, -6, 0, true);
+ ret = pa_memblockq_push(bq, &chunk2);
+ fail_unless(ret == 0);
+
+ /* Test splitting */
+ pa_memblockq_seek(bq, -12, 0, true);
+ ret = pa_memblockq_push(bq, &chunk1);
+ fail_unless(ret == 0);
+
+ pa_memblockq_seek(bq, 20, 0, true);
+
+ /* Test merging */
+ ret = pa_memblockq_push(bq, &chunk3);
+ fail_unless(ret == 0);
+ pa_memblockq_seek(bq, -2, 0, true);
+
+ chunk3.index += 2;
+ chunk3.length -= 2;
+ ret = pa_memblockq_push(bq, &chunk3);
+ fail_unless(ret == 0);
+
+ pa_memblockq_seek(bq, 30, PA_SEEK_RELATIVE, true);
+
+ dump(bq, 0);
+
+ pa_memblockq_rewind(bq, 52);
+
+ dump(bq, 1);
+
+ check_queue_invariants(bq);
+
+ pa_memblockq_free(bq);
+ pa_memblock_unref(silence.memblock);
+ pa_memblock_unref(chunk1.memblock);
+ pa_memblock_unref(chunk2.memblock);
+ pa_memblock_unref(chunk3.memblock);
+ pa_memblock_unref(chunk4.memblock);
+
+ pa_mempool_unref(p);
+}
+END_TEST
+
+START_TEST (memblockq_test_length_changes) {
+ pa_mempool *p;
+ pa_memblockq *bq;
+ pa_memchunk silence, data;
+ pa_sample_spec ss = {
+ .format = PA_SAMPLE_S32BE,
+ .rate = 48000,
+ .channels = 1
+ };
+ int64_t idx = 0;
+ size_t maxlength = 60;
+ size_t tlength = 40;
+ size_t prebuf = 16;
+ size_t minreq = 20;
+ size_t maxrewind = 40;
+
+ p = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true);
+ ck_assert_ptr_ne(p, NULL);
+
+ silence = memchunk_from_str(p, "____");
+
+ bq = pa_memblockq_new("test memblockq", idx, maxlength, tlength, &ss, prebuf, minreq, maxrewind, &silence);
+ fail_unless(bq != NULL);
+
+ data = memchunk_from_str(p, "12345678");
+
+ /* insert some data */
+ ck_assert_int_eq(pa_memblockq_push(bq, &data), 0);
+ ck_assert_int_eq(pa_memblockq_push(bq, &data), 0);
+ ck_assert_int_eq(pa_memblockq_push(bq, &data), 0);
+ ck_assert_int_eq(pa_memblockq_push(bq, &data), 0);
+
+ /* check state */
+ ck_assert_int_eq(pa_memblockq_get_length(bq), 32);
+
+ /* adjust maximum length
+ * This might modify tlength, prebuf, minreq, too. */
+ pa_memblockq_set_maxlength(bq, maxlength/2);
+ check_queue_invariants(bq);
+
+ /* adjust target length
+ * This might modify minreq, too. */
+ pa_memblockq_set_tlength(bq, tlength/2);
+ check_queue_invariants(bq);
+
+ /* adjust minimum requested length
+ * This might modify prebuf, too. */
+ pa_memblockq_set_minreq(bq, minreq/2);
+ check_queue_invariants(bq);
+
+ /* adjust prebuffer length */
+ pa_memblockq_set_prebuf(bq, prebuf/2);
+ check_queue_invariants(bq);
+
+ /* cleanup */
+ pa_memblockq_free(bq);
+ pa_memblock_unref(silence.memblock);
+ pa_memblock_unref(data.memblock);
+ pa_mempool_unref(p);
+}
+END_TEST
+
+START_TEST (memblockq_test_pop_missing) {
+ pa_mempool *p;
+ pa_memblockq *bq;
+ pa_memchunk silence, data, chunk;
+ pa_sample_spec ss = {
+ .format = PA_SAMPLE_S16BE,
+ .rate = 48000,
+ .channels = 1
+ };
+ int64_t idx = 0;
+ size_t maxlength = 200;
+ size_t tlength = 100;
+ size_t prebuf = 0;
+ size_t minreq = 80;
+ size_t maxrewind = 0;
+
+ p = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true);
+ ck_assert_ptr_ne(p, NULL);
+
+ silence = memchunk_from_str(p, "____");
+ data = memchunk_from_str(p, "1234567890");
+
+ bq = pa_memblockq_new("test memblockq", idx, maxlength, tlength, &ss, prebuf, minreq, maxrewind, &silence);
+ fail_unless(bq != NULL);
+
+ /* The following equation regarding the internal variables of a memblockq
+ * is always true:
+ *
+ * length + missing + requested = tlength
+ *
+ * "length" is the current memblockq length (write index minus read index)
+ * and "tlength" is the target length. The intuitive meaning of "missing"
+ * would be the difference between tlength and length, but actually
+ * "missing" and "requested" together constitute the amount that is missing
+ * from the queue. Writing to the queue decrements "requested" and reading
+ * from the queue increments "missing". pa_memblockq_pop_missing() resets
+ * "missing" to zero, returns the old "missing" value and adds the
+ * equivalent amount to "requested".
+ *
+ * This test has comments between each step documenting the assumed state
+ * of those internal variables. */
+
+ /* length + missing + requested = tlength
+ * 0 + 100 + 0 = 100 */
+
+ ck_assert_int_eq(pa_memblockq_pop_missing(bq), tlength);
+
+ /* length + missing + requested = tlength
+ * 0 + 0 + 100 = 100 */
+
+ for (int i = 0; i != 2; ++i)
+ ck_assert_int_eq(pa_memblockq_push(bq, &data), 0);
+ check_queue_invariants(bq);
+
+ /* length + missing + requested = tlength
+ * 20 + 0 + 80 = 100 */
+
+ ck_assert_int_eq(pa_memblockq_pop_missing(bq), 0);
+
+ /* length + missing + requested = tlength
+ * 20 + 0 + 80 = 100 */
+
+ for (int i = 0; i != 8; ++i)
+ ck_assert_int_eq(pa_memblockq_push(bq, &data), 0);
+ check_queue_invariants(bq);
+
+ /* length + missing + requested = tlength
+ * 100 + 0 + 0 = 100 */
+
+ ck_assert_int_eq(pa_memblockq_pop_missing(bq), 0);
+
+ /* length + missing + requested = tlength
+ * 100 + 0 + 0 = 100 */
+
+ ck_assert_int_eq(pa_memblockq_peek_fixed_size(bq, 40, &chunk), 0);
+ pa_memblockq_drop(bq, 40);
+ ck_assert_int_eq(chunk.length - chunk.index, 40);
+ pa_memblock_unref(chunk.memblock);
+ check_queue_invariants(bq);
+
+ /* length + missing + requested = tlength
+ * 60 + 40 + 0 = 100 */
+
+ /* 40 bytes are missing, but that's less than minreq, so 0 is reported */
+ ck_assert_int_eq(pa_memblockq_pop_missing(bq), 0);
+
+ /* length + missing + requested = tlength
+ * 60 + 40 + 0 = 100 */
+
+ /* Now we push 30 bytes even though it was not requested, so the requested
+ * counter goes negative! */
+ for (int i = 0; i != 3; ++i)
+ ck_assert_int_eq(pa_memblockq_push(bq, &data), 0);
+ check_queue_invariants(bq);
+
+ /* length + missing + requested = tlength
+ * 90 + 40 + -30 = 100 */
+
+ /* missing < minreq, so nothing is reported missing. */
+ ck_assert_int_eq(pa_memblockq_pop_missing(bq), 0);
+
+ /* length + missing + requested = tlength
+ * 90 + 40 + -30 = 100 */
+
+ ck_assert_int_eq(pa_memblockq_peek_fixed_size(bq, 20, &chunk), 0);
+ pa_memblockq_drop(bq, 20);
+ ck_assert_int_eq(chunk.length - chunk.index, 20);
+ pa_memblock_unref(chunk.memblock);
+ check_queue_invariants(bq);
+
+ /* length + missing + requested = tlength
+ * 70 + 60 + -30 = 100 */
+
+ /* missing < minreq, so nothing is reported missing. */
+ ck_assert_int_eq(pa_memblockq_pop_missing(bq), 0);
+
+ /* length + missing + requested = tlength
+ * 70 + 60 + -30 = 100 */
+
+ /* We push more data again even though it was not requested, so the
+ * requested counter goes further into the negative range. */
+ for (int i = 0; i != 5; ++i)
+ ck_assert_int_eq(pa_memblockq_push(bq, &data), 0);
+ check_queue_invariants(bq);
+
+ /* length + missing + requested = tlength
+ * 120 + 60 + -80 = 100 */
+
+ /* missing < minreq, so nothing is reported missing. */
+ ck_assert_int_eq(pa_memblockq_pop_missing(bq), 0);
+
+ /* length + missing + requested = tlength
+ * 120 + 60 + -80 = 100 */
+
+ ck_assert_int_eq(pa_memblockq_peek_fixed_size(bq, 20, &chunk), 0);
+ pa_memblockq_drop(bq, 20);
+ ck_assert_int_eq(chunk.length - chunk.index, 20);
+ pa_memblock_unref(chunk.memblock);
+ check_queue_invariants(bq);
+
+ /* length + missing + requested = tlength
+ * 100 + 80 + -80 = 100 */
+
+ /* missing has now reached the minreq threshold */
+ ck_assert_int_eq(pa_memblockq_pop_missing(bq), 80);
+
+ /* length + missing + requested = tlength
+ * 100 + 0 + 0 = 100 */
+
+ /* cleanup */
+ pa_memblockq_free(bq);
+ pa_memblock_unref(silence.memblock);
+ pa_memblock_unref(data.memblock);
+ pa_mempool_unref(p);
+}
+END_TEST
+
+START_TEST (memblockq_test_tlength_change) {
+ int ret;
+ size_t missing;
+
+ pa_mempool *p;
+ pa_memblockq *bq;
+ pa_memchunk chunk;
+ char buffer[2048];
+ pa_sample_spec ss = {
+ .format = PA_SAMPLE_S16LE,
+ .rate = 48000,
+ .channels = 1
+ };
+
+ pa_log_set_level(PA_LOG_DEBUG);
+
+ bq = pa_memblockq_new("test memblockq", 0, 4096, 2048, &ss, 0, 512, 512, NULL);
+ fail_unless(bq != NULL);
+
+ /* Empty buffer, so expect tlength */
+ missing = pa_memblockq_pop_missing(bq);
+ fail_unless(missing == 2048);
+
+ /* Everything requested, so should be satisfied */
+ missing = pa_memblockq_pop_missing(bq);
+ fail_unless(missing == 0);
+
+ p = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true);
+
+ chunk.memblock = pa_memblock_new_fixed(p, buffer, sizeof(buffer), 1);
+ fail_unless(chunk.memblock != NULL);
+
+ chunk.index = 0;
+ chunk.length = sizeof(buffer);
+
+ /* Fill buffer (i.e. satisfy earlier request) */
+ ret = pa_memblockq_push(bq, &chunk);
+ fail_unless(ret == 0);
+
+ /* Should still be happy */
+ missing = pa_memblockq_pop_missing(bq);
+ fail_unless(missing == 0);
+
+ /* Check that we don't request less than minreq */
+ pa_memblockq_drop(bq, 400);
+ missing = pa_memblockq_pop_missing(bq);
+ ck_assert_int_eq(missing, 0);
+
+ missing = pa_memblockq_pop_missing(bq);
+ fail_unless(missing == 0);
+
+ /* Reduce tlength under what's dropped and under previous minreq */
+ pa_memblockq_set_tlength(bq, 256);
+ pa_memblockq_set_minreq(bq, 64);
+
+ /* We are now overbuffered and should not request more */
+ missing = pa_memblockq_pop_missing(bq);
+ fail_unless(missing == 0);
+
+ /* Drop more data so we are below tlength again, but just barely */
+ pa_memblockq_drop(bq, 1400);
+
+ /* Should still honour minreq */
+ missing = pa_memblockq_pop_missing(bq);
+ fail_unless(missing == 0);
+
+ /* Finally drop enough to fall below minreq */
+ pa_memblockq_drop(bq, 80);
+
+ /* And expect a request */
+ missing = pa_memblockq_pop_missing(bq);
+ fail_unless(missing == 88);
+
+ pa_memblockq_free(bq);
+ pa_memblock_unref(chunk.memblock);
+ pa_mempool_unref(p);
+}
+END_TEST
+
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ if (!getenv("MAKE_CHECK"))
+ pa_log_set_level(PA_LOG_DEBUG);
+
+ s = suite_create("Memblock Queue");
+ tc = tcase_create("memblockq");
+ tcase_add_test(tc, memchunk_from_str_test);
+ tcase_add_test(tc, memblockq_test_initial_properties);
+ tcase_add_test(tc, memblockq_test);
+ tcase_add_test(tc, memblockq_test_length_changes);
+ tcase_add_test(tc, memblockq_test_pop_missing);
+ tcase_add_test(tc, memblockq_test_tlength_change);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/meson.build b/src/tests/meson.build
new file mode 100644
index 0000000..5caf3ab
--- /dev/null
+++ b/src/tests/meson.build
@@ -0,0 +1,253 @@
+# Note that a few tests have dependencies on src/modules.
+#
+# The syntax for tests declaration is:
+#
+# test name, sources, deps, [extra libs, extra flags]
+#
+
+# Default tests
+
+default_tests = [
+ [ 'asyncmsgq-test', 'asyncmsgq-test.c',
+ [ check_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'asyncq-test', 'asyncq-test.c',
+ [ check_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'channelmap-test', 'channelmap-test.c',
+ [ check_dep, libpulse_dep ] ],
+ [ 'close-test', 'close-test.c',
+ [ libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'core-util-test', 'core-util-test.c',
+ [ check_dep, libpulse_dep, libpulsecommon_dep ] ],
+ [ 'cpu-mix-test', [ 'cpu-mix-test.c', 'runtime-test-util.h' ],
+ [ check_dep, libm_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'cpu-remap-test', [ 'cpu-remap-test.c', 'runtime-test-util.h' ],
+ [ check_dep, libm_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'cpu-sconv-test', [ 'cpu-sconv-test.c', 'runtime-test-util.h' ],
+ [ check_dep, libm_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'cpu-volume-test', [ 'cpu-volume-test.c', 'runtime-test-util.h' ],
+ [ check_dep, libm_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'format-test', 'format-test.c',
+ [ check_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'get-binary-name-test', 'get-binary-name-test.c',
+ [ check_dep, libpulse_dep, libpulsecommon_dep ] ],
+ [ 'hashmap-test', 'hashmap-test.c',
+ [ check_dep, libpulse_dep, libpulsecommon_dep ] ],
+ [ 'hook-list-test', 'hook-list-test.c',
+ [ check_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'json-test', 'json-test.c',
+ [ check_dep, libpulse_dep, libpulsecommon_dep ] ],
+ [ 'lfe-filter-test', 'lfe-filter-test.c',
+ [ check_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'lock-autospawn-test', 'lock-autospawn-test.c',
+ [ check_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'mainloop-test', 'mainloop-test.c',
+ [ check_dep, libpulse_dep, libpulsecommon_dep ] ],
+ [ 'memblock-test', 'memblock-test.c',
+ [ check_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'memblockq-test', 'memblockq-test.c',
+ [ check_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'mix-test', 'mix-test.c',
+ [ check_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'mult-s16-test', [ 'mult-s16-test.c', 'runtime-test-util.h' ],
+ [ check_dep, libm_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'proplist-test', 'proplist-test.c',
+ [ check_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'queue-test', 'queue-test.c',
+ [ check_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'resampler-test', 'resampler-test.c',
+ [ libpulse_dep, libpulsecommon_dep, libpulsecore_dep, libintl_dep ] ],
+ [ 'rtpoll-test', 'rtpoll-test.c',
+ [ check_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'smoother-test', 'smoother-test.c',
+ [ check_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'strlist-test', 'strlist-test.c',
+ [ check_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'thread-mainloop-test', 'thread-mainloop-test.c',
+ [ check_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'thread-test', 'thread-test.c',
+ [ check_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'utf8-test', 'utf8-test.c',
+ [ check_dep, libpulse_dep, libpulsecommon_dep ] ],
+ [ 'volume-test', 'volume-test.c',
+ [ check_dep, libm_dep, libpulse_dep, libpulsecommon_dep ] ],
+]
+
+if host_machine.system() != 'windows'
+ default_tests += [
+ [ 'sigbus-test', 'sigbus-test.c',
+ [ check_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'usergroup-test', 'usergroup-test.c',
+ [ check_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ ]
+endif
+
+if host_machine.system() != 'darwin'
+ default_tests += [
+ [ 'once-test', 'once-test.c',
+ [ check_dep, thread_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep, libatomic_ops_dep ] ],
+ ]
+endif
+
+if cc.has_header('sys/eventfd.h')
+ default_tests += [
+ [ 'srbchannel-test', 'srbchannel-test.c',
+ [ check_dep, libpulse_dep, libpulsecommon_dep ] ]
+ ]
+endif
+
+if alsa_dep.found()
+ default_tests += [
+ [ 'alsa-mixer-path-test', 'alsa-mixer-path-test.c',
+ [ alsa_dep, check_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ],
+ libalsa_util ]
+ ]
+endif
+
+if glib_dep.found()
+ default_tests += [
+ [ 'mainloop-test-glib', 'mainloop-test.c',
+ [ check_dep, glib_dep, libpulse_dep, libpulsecommon_dep, libpulse_mainloop_glib_dep ],
+ [], ['-DGLIB_MAIN_LOOP'] ]
+ ]
+endif
+
+# No-run tests
+
+norun_tests = [
+ [ 'flist-test', 'flist-test.c',
+ [ libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'ipacl-test', 'ipacl-test.c',
+ [ check_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'lo-latency-test', [ 'lo-latency-test.c', 'lo-test-util.c', 'lo-test-util.h' ],
+ [ check_dep, libm_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'mcalign-test', 'mcalign-test.c',
+ [ libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'pacat-simple', 'pacat-simple.c',
+ [ libpulse_dep, libpulse_simple_dep ] ],
+ [ 'parec-simple', 'parec-simple.c',
+ [ libpulse_dep, libpulse_simple_dep ] ],
+ [ 'remix-test', 'remix-test.c',
+ [ libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'rtstutter', 'rtstutter.c',
+ [ thread_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'sig2str-test', 'sig2str-test.c',
+ [ check_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'stripnul', 'stripnul.c',
+ [ libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+]
+
+# echo-cancel test is a bit tedious to handle
+echo_cancel_test_sources = []
+foreach s : module_echo_cancel_sources
+ echo_cancel_test_sources += '../modules/' + s
+endforeach
+echo_cancel_test_sources += module_echo_cancel_orc_sources
+
+norun_tests += [
+ [ 'echo-cancel-test', echo_cancel_test_sources,
+ module_echo_cancel_deps + [ libpulse_dep, libpulsecommon_dep, libpulsecore_dep, libintl_dep ],
+ module_echo_cancel_libs,
+ module_echo_cancel_flags + server_c_args + [ '-DPA_MODULE_NAME=module_echo_cancel', '-DECHO_CANCEL_TEST=1' ] ]
+]
+
+if cc.has_header_symbol('signal.h', 'SIGXCPU')
+ norun_tests += [
+ [ 'cpulimit-test', [ 'cpulimit-test.c', '../daemon/cpulimit.c', '../daemon/cpulimit.h' ],
+ [ check_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+ [ 'cpulimit-test2', [ 'cpulimit-test.c', '../daemon/cpulimit.c', '../daemon/cpulimit.h' ],
+ [ check_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ],
+ [], ['-DTEST2'] ],
+ ]
+endif
+
+if cc.has_function('pthread_setaffinity_np', dependencies : thread_dep)
+ norun_tests += [
+ [ 'atomic-test', 'atomic-test.c',
+ [ check_dep, libpulsecommon_dep, thread_dep ] ]
+ ]
+endif
+
+if alsa_dep.found()
+ norun_tests += [
+ [ 'alsa-time-test', 'alsa-time-test.c', [ alsa_dep ] ]
+ ]
+endif
+
+if gtk_dep.found() and glib_dep.found()
+ norun_tests += [
+ [ 'gtk-test', 'gtk-test.c',
+ [ gtk_dep, libpulse_dep, libpulse_mainloop_glib_dep ] ]
+ ]
+endif
+
+# Generate tests
+
+test_env = environment()
+test_env.set('MAKE_CHECK', '1')
+
+foreach t : default_tests + norun_tests
+ name = t[0]
+ sources = t[1]
+ deps = t[2]
+ extra_libs = t.get(3, [])
+ extra_flags = t.get(4, [])
+
+ exe = executable(name, sources,
+ c_args : pa_c_args + extra_flags,
+ include_directories : [ configinc, topinc ],
+ dependencies : deps,
+ link_with : extra_libs,
+ )
+
+ if default_tests.contains(t)
+ test(name, exe,
+ env : test_env,
+ timeout : 300,
+ )
+ endif
+endforeach
+
+# These tests need a running pulseaudio daemon
+
+daemon_tests = [
+ [ 'extended-test', 'extended-test.c',
+ [ check_dep, libm_dep, libpulse_dep ] ],
+ [ 'sync-playback', 'sync-playback.c',
+ [ check_dep, libm_dep, libpulse_dep ] ],
+]
+
+daemon_tests_long = [
+ [ 'connect-stress', 'connect-stress.c',
+ [ check_dep, libpulse_dep ] ],
+ [ 'interpol-test', 'interpol-test.c',
+ [ check_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep ] ],
+]
+
+daemon_test_names = []
+daemon_test_long_names = []
+
+foreach t : daemon_tests + daemon_tests_long
+ name = t[0]
+ sources = t[1]
+ deps = t[2]
+
+ if daemon_tests.contains(t)
+ daemon_test_names += name
+ else
+ daemon_test_long_names += name
+ endif
+
+ executable(name, sources,
+ c_args : pa_c_args,
+ include_directories : [ configinc, topinc ],
+ dependencies : deps,
+ )
+endforeach
+
+test_daemon_meson_sh = find_program('test-daemon.meson.sh')
+run_target('test-daemon',
+ command : [ test_daemon_meson_sh ] + daemon_test_names
+)
+run_target('test-daemon-long',
+ command : [ test_daemon_meson_sh ] + daemon_test_long_names
+)
diff --git a/src/tests/mix-test.c b/src/tests/mix-test.c
new file mode 100644
index 0000000..d9255e3
--- /dev/null
+++ b/src/tests/mix-test.c
@@ -0,0 +1,362 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+
+#include <check.h>
+
+#include <pulse/sample.h>
+#include <pulse/volume.h>
+
+#include <pulsecore/macro.h>
+#include <pulsecore/endianmacros.h>
+#include <pulsecore/memblock.h>
+#include <pulsecore/sample-util.h>
+#include <pulsecore/mix.h>
+
+/* PA_SAMPLE_U8 */
+static const uint8_t u8_result[3][10] = {
+{ 0x00, 0xff, 0x7f, 0x80, 0x9f, 0x3f, 0x01, 0xf0, 0x20, 0x21 },
+{ 0x0c, 0xf2, 0x7f, 0x80, 0x9b, 0x45, 0x0d, 0xe4, 0x29, 0x2a },
+{ 0x00, 0xff, 0x7e, 0x80, 0xba, 0x04, 0x00, 0xff, 0x00, 0x00 },
+};
+
+/* PA_SAMPLE_ALAW */
+static const uint8_t alaw_result[3][10] = {
+{ 0x00, 0xff, 0x7f, 0x80, 0x9f, 0x3f, 0x01, 0xf0, 0x20, 0x21 },
+{ 0x06, 0xf2, 0x72, 0x86, 0x92, 0x32, 0x07, 0xf6, 0x26, 0x27 },
+{ 0x31, 0xec, 0x6d, 0xb1, 0x8c, 0x2d, 0x36, 0xe1, 0x2a, 0x2a },
+};
+
+/* PA_SAMPLE_ULAW */
+static const uint8_t ulaw_result[3][10] = {
+{ 0x00, 0xff, 0x7f, 0x80, 0x9f, 0x3f, 0x01, 0xf0, 0x20, 0x21 },
+{ 0x03, 0xff, 0xff, 0x83, 0xa2, 0x42, 0x04, 0xf2, 0x23, 0x24 },
+{ 0x00, 0xff, 0xff, 0x80, 0x91, 0x31, 0x00, 0xe9, 0x12, 0x13 },
+};
+
+static const uint16_t s16ne_result[3][10] = {
+{ 0x0000, 0xffff, 0x7fff, 0x8000, 0x9fff, 0x3fff, 0x0001, 0xf000, 0x0020, 0x0021 },
+{ 0x0000, 0xffff, 0x7332, 0x8ccd, 0xa998, 0x3998, 0x0000, 0xf199, 0x001c, 0x001d },
+{ 0x0000, 0xfffe, 0x7fff, 0x8000, 0x8000, 0x7997, 0x0001, 0xe199, 0x003c, 0x003e },
+};
+
+static const float float32ne_result[3][10] = {
+{ 0.000000, -1.000000, 1.000000, 4711.000000, 0.222000, 0.330000, -0.300000, 99.000000, -0.555000, -0.123000 },
+{ 0.000000, -0.899987, 0.899987, 4239.837402, 0.199797, 0.296996, -0.269996, 89.098679, -0.499493, -0.110698 },
+{ 0.000000, -1.899987, 1.899987, 8950.837891, 0.421797, 0.626996, -0.569996, 188.098679, -1.054493, -0.233698 },
+};
+
+static const uint32_t s32ne_result[3][10] = {
+{ 0x00000001, 0xffff0002, 0x7fff0003, 0x80000004, 0x9fff0005, 0x3fff0006, 0x00010007, 0xf0000008, 0x00200009, 0x0021000a },
+{ 0x00000000, 0xffff199b, 0x7332199c, 0x8ccd0003, 0xa998d99e, 0x3998999f, 0x0000e66c, 0xf199a007, 0x001cccc8, 0x001db32e },
+{ 0x00000001, 0xfffe199d, 0x7fffffff, 0x80000000, 0x80000000, 0x799799a5, 0x0001e673, 0xe199a00f, 0x003cccd1, 0x003eb338 },
+};
+
+/* attention: result is in BE, not NE! */
+static const uint8_t s24be_result[3][30] = {
+{ 0x00, 0x00, 0x01, 0xff, 0xff, 0x02, 0x7f, 0xff, 0x03, 0x80, 0x00, 0x04, 0x9f, 0xff, 0x05, 0x3f, 0xff, 0x06, 0x01, 0x00, 0x07, 0xf0, 0x00, 0x08, 0x20, 0x00, 0x09, 0x21, 0x00, 0x0a },
+{ 0x00, 0x00, 0x00, 0xff, 0xff, 0x1b, 0x73, 0x32, 0x1c, 0x8c, 0xcd, 0x03, 0xa9, 0x98, 0xde, 0x39, 0x98, 0x9f, 0x00, 0xe6, 0x6c, 0xf1, 0x99, 0xa7, 0x1c, 0xcc, 0xc8, 0x1d, 0xb3, 0x2e },
+{ 0x00, 0x00, 0x01, 0xff, 0xfe, 0x1d, 0x7f, 0xff, 0xff, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x79, 0x97, 0xa5, 0x01, 0xe6, 0x73, 0xe1, 0x99, 0xaf, 0x3c, 0xcc, 0xd1, 0x3e, 0xb3, 0x38 },
+};
+
+static const uint32_t s24_32ne_result[3][10] = {
+{ 0x00000001, 0xffff0002, 0x7fff0003, 0x80000004, 0x9fff0005, 0x3fff0006, 0x00010007, 0xf0000008, 0x00200009, 0x0021000a },
+{ 0x00000000, 0x00ff199b, 0x00ff199c, 0x00000003, 0x00ff199e, 0x00ff199f, 0x0000e66c, 0x00000007, 0x001cccc8, 0x001db32e },
+{ 0x00000001, 0x00fe199d, 0x00fe199f, 0x00000007, 0x00fe19a3, 0x00fe19a5, 0x0001e673, 0x0000000f, 0x003cccd1, 0x003eb338 },
+};
+
+static void compare_block(const pa_sample_spec *ss, const pa_memchunk *chunk, int iter) {
+ void *d;
+ unsigned i;
+
+ d = pa_memblock_acquire(chunk->memblock);
+
+ switch (ss->format) {
+ case PA_SAMPLE_U8: {
+ const uint8_t *v = u8_result[iter];
+ uint8_t *u = d;
+
+ for (i = 0; i < chunk->length / pa_frame_size(ss); i++) {
+ fail_unless(*u == *v, NULL);
+ ++u;
+ ++v;
+ }
+ break;
+ }
+
+ case PA_SAMPLE_ALAW: {
+ const uint8_t *v = alaw_result[iter];
+ uint8_t *u = d;
+
+ for (i = 0; i < chunk->length / pa_frame_size(ss); i++) {
+ fail_unless(*u == *v, NULL);
+ ++u;
+ ++v;
+ }
+ break;
+ }
+
+ case PA_SAMPLE_ULAW: {
+ const uint8_t *v = ulaw_result[iter];
+ uint8_t *u = d;
+
+ for (i = 0; i < chunk->length / pa_frame_size(ss); i++) {
+ fail_unless(*u == *v, NULL);
+ ++u;
+ ++v;
+ }
+ break;
+ }
+
+ case PA_SAMPLE_S16NE:
+ case PA_SAMPLE_S16RE: {
+ const uint16_t *v = s16ne_result[iter];
+ uint16_t *u = d;
+
+ for (i = 0; i < chunk->length / pa_frame_size(ss); i++) {
+ uint16_t uu = PA_MAYBE_UINT16_SWAP(ss->format != PA_SAMPLE_S16NE, *u);
+ fail_unless(uu == *v, NULL);
+ ++u;
+ ++v;
+ }
+ break;
+ }
+
+ case PA_SAMPLE_FLOAT32NE:
+ case PA_SAMPLE_FLOAT32RE: {
+ const float *v = float32ne_result[iter];
+ float *u = d;
+
+ for (i = 0; i < chunk->length / pa_frame_size(ss); i++) {
+ float uu = ss->format == PA_SAMPLE_FLOAT32NE ? *u : PA_READ_FLOAT32RE(u);
+ fail_unless(fabsf(uu - *v) <= 1e-6f, NULL);
+ ++u;
+ ++v;
+ }
+ break;
+ }
+
+ case PA_SAMPLE_S32NE:
+ case PA_SAMPLE_S32RE: {
+ const uint32_t *v = s32ne_result[iter];
+ uint32_t *u = d;
+
+ for (i = 0; i < chunk->length / pa_frame_size(ss); i++) {
+ uint32_t uu = PA_MAYBE_UINT32_SWAP(ss->format != PA_SAMPLE_S32NE, *u);
+ fail_unless(uu == *v, NULL);
+ ++u;
+ ++v;
+ }
+ break;
+ }
+
+ case PA_SAMPLE_S24_32NE:
+ case PA_SAMPLE_S24_32RE: {
+ const uint32_t *v = s24_32ne_result[iter];
+ uint32_t *u = d;
+
+ for (i = 0; i < chunk->length / pa_frame_size(ss); i++) {
+ uint32_t uu = PA_MAYBE_UINT32_SWAP(ss->format != PA_SAMPLE_S24_32NE, *u);
+ fail_unless(uu == *v, NULL);
+ ++u;
+ ++v;
+ }
+ break;
+ }
+
+ case PA_SAMPLE_S24NE:
+ case PA_SAMPLE_S24RE: {
+ const uint8_t *v = s24be_result[iter];
+ uint8_t *u = d;
+
+ for (i = 0; i < chunk->length / pa_frame_size(ss); i++) {
+ uint32_t uu = ss->format == PA_SAMPLE_S24LE ? PA_READ24LE(u) : PA_READ24BE(u);
+ fail_unless(uu == PA_READ24BE(v), NULL);
+
+ u += 3;
+ v += 3;
+ }
+ break;
+ }
+
+ default:
+ pa_assert_not_reached();
+ }
+
+ pa_memblock_release(chunk->memblock);
+}
+
+static pa_memblock* generate_block(pa_mempool *pool, const pa_sample_spec *ss) {
+ pa_memblock *r;
+ void *d;
+ unsigned i;
+
+ pa_assert_se(r = pa_memblock_new(pool, pa_frame_size(ss) * 10));
+ d = pa_memblock_acquire(r);
+
+ switch (ss->format) {
+
+ case PA_SAMPLE_U8:
+ case PA_SAMPLE_ULAW:
+ case PA_SAMPLE_ALAW: {
+ memcpy(d, u8_result[0], sizeof(u8_result[0]));
+ break;
+ }
+
+ case PA_SAMPLE_S16NE:
+ case PA_SAMPLE_S16RE: {
+ if (ss->format == PA_SAMPLE_S16RE) {
+ uint16_t *u = d;
+ for (i = 0; i < 10; i++)
+ u[i] = PA_UINT16_SWAP(s16ne_result[0][i]);
+ } else
+ memcpy(d, s16ne_result[0], sizeof(s16ne_result[0]));
+ break;
+ }
+
+ case PA_SAMPLE_S24_32NE:
+ case PA_SAMPLE_S24_32RE:
+ case PA_SAMPLE_S32NE:
+ case PA_SAMPLE_S32RE: {
+ if (ss->format == PA_SAMPLE_S24_32RE || ss->format == PA_SAMPLE_S32RE) {
+ uint32_t *u = d;
+ for (i = 0; i < 10; i++)
+ u[i] = PA_UINT32_SWAP(s32ne_result[0][i]);
+ } else
+ memcpy(d, s32ne_result[0], sizeof(s32ne_result[0]));
+ break;
+ }
+
+ case PA_SAMPLE_S24NE:
+ case PA_SAMPLE_S24RE:
+ if (ss->format == PA_SAMPLE_S24LE) {
+ uint8_t *u = d;
+ for (i = 0; i < 30; i += 3)
+ PA_WRITE24LE(&u[i], PA_READ24BE(&s24be_result[0][i]));
+ } else
+ memcpy(d, s24be_result[0], sizeof(s24be_result[0]));
+ break;
+
+ case PA_SAMPLE_FLOAT32NE:
+ case PA_SAMPLE_FLOAT32RE: {
+ if (ss->format == PA_SAMPLE_FLOAT32RE) {
+ float *u = d;
+ for (i = 0; i < 10; i++)
+ PA_WRITE_FLOAT32RE(&u[i], float32ne_result[0][i]);
+ } else
+ memcpy(d, float32ne_result[0], sizeof(float32ne_result[0]));
+
+ break;
+ }
+
+ default:
+ pa_assert_not_reached();
+ }
+
+ pa_memblock_release(r);
+
+ return r;
+}
+
+START_TEST (mix_test) {
+ pa_mempool *pool;
+ pa_sample_spec a;
+ pa_cvolume v;
+
+ if (!getenv("MAKE_CHECK"))
+ pa_log_set_level(PA_LOG_DEBUG);
+
+ fail_unless((pool = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true)) != NULL, NULL);
+
+ a.channels = 1;
+ a.rate = 44100;
+
+ v.channels = a.channels;
+ v.values[0] = pa_sw_volume_from_linear(0.9);
+
+ for (a.format = 0; a.format < PA_SAMPLE_MAX; a.format ++) {
+ pa_memchunk i, j, k;
+ pa_mix_info m[2];
+ void *ptr;
+
+ pa_log_debug("=== mixing: %s", pa_sample_format_to_string(a.format));
+
+ /* Generate block */
+ i.memblock = generate_block(pool, &a);
+ i.length = pa_memblock_get_length(i.memblock);
+ i.index = 0;
+
+ /* Make a copy */
+ j = i;
+ pa_memblock_ref(j.memblock);
+ pa_memchunk_make_writable(&j, 0);
+
+ /* Adjust volume of the copy */
+ pa_volume_memchunk(&j, &a, &v);
+
+ compare_block(&a, &j, 1);
+
+ m[0].chunk = i;
+ m[0].volume.values[0] = PA_VOLUME_NORM;
+ m[0].volume.channels = a.channels;
+ m[1].chunk = j;
+ m[1].volume.values[0] = PA_VOLUME_NORM;
+ m[1].volume.channels = a.channels;
+
+ k.memblock = pa_memblock_new(pool, i.length);
+ k.length = i.length;
+ k.index = 0;
+
+ ptr = pa_memblock_acquire_chunk(&k);
+ pa_mix(m, 2, ptr, k.length, &a, NULL, false);
+ pa_memblock_release(k.memblock);
+
+ compare_block(&a, &k, 2);
+
+ pa_memblock_unref(i.memblock);
+ pa_memblock_unref(j.memblock);
+ pa_memblock_unref(k.memblock);
+ }
+
+ pa_mempool_unref(pool);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ s = suite_create("Mix");
+ tc = tcase_create("mix");
+ tcase_add_test(tc, mix_test);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/mult-s16-test.c b/src/tests/mult-s16-test.c
new file mode 100644
index 0000000..91740c2
--- /dev/null
+++ b/src/tests/mult-s16-test.c
@@ -0,0 +1,114 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <check.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include <pulse/rtclock.h>
+#include <pulsecore/random.h>
+#include <pulsecore/macro.h>
+
+#include "runtime-test-util.h"
+
+static inline int32_t pa_mult_s16_volume_32(int16_t v, int32_t cv) {
+ /* Multiplying the 32 bit volume factor with the
+ * 16 bit sample might result in an 48 bit value. We
+ * want to do without 64 bit integers and hence do
+ * the multiplication independently for the HI and
+ * LO part of the volume. */
+ int32_t hi = cv >> 16;
+ int32_t lo = cv & 0xFFFF;
+ return ((v * lo) >> 16) + (v * hi);
+}
+
+static inline int32_t pa_mult_s16_volume_64(int16_t v, int32_t cv) {
+ /* Multiply with 64 bit integers on 64 bit platforms */
+ return (v * (int64_t) cv) >> 16;
+}
+
+#define SAMPLES 1028
+#define TIMES 10000
+#define TIMES2 100
+
+START_TEST (mult_s16_test) {
+ int16_t samples[SAMPLES];
+ int32_t volumes[SAMPLES];
+ int32_t sum1 = 0, sum2 = 0;
+ int i;
+
+ pa_random(samples, sizeof(samples));
+ pa_random(volumes, sizeof(volumes));
+
+ for (i = 0; i < SAMPLES; i++) {
+ int32_t a = pa_mult_s16_volume_32(samples[i], volumes[i]);
+ int32_t b = pa_mult_s16_volume_64(samples[i], volumes[i]);
+
+ if (a != b) {
+ pa_log_debug("%d: %d != %d", i, a, b);
+ ck_abort();
+ }
+ }
+
+ PA_RUNTIME_TEST_RUN_START("32 bit mult", TIMES, TIMES2) {
+ for (i = 0; i < SAMPLES; i++) {
+ sum1 += pa_mult_s16_volume_32(samples[i], volumes[i]);
+ }
+ } PA_RUNTIME_TEST_RUN_STOP
+
+ PA_RUNTIME_TEST_RUN_START("64 bit mult", TIMES, TIMES2) {
+ for (i = 0; i < SAMPLES; i++)
+ sum2 += pa_mult_s16_volume_64(samples[i], volumes[i]);
+ } PA_RUNTIME_TEST_RUN_STOP
+
+ fail_unless(sum1 == sum2);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ if (!getenv("MAKE_CHECK"))
+ pa_log_set_level(PA_LOG_DEBUG);
+
+#ifdef HAVE_FAST_64BIT_OPERATIONS
+ pa_log_debug("Detected CPU with fast 64-bit operations.");
+#else
+ pa_log_debug("Not detected CPU with fast 64-bit operations.");
+#endif
+
+ s = suite_create("Mult-s16");
+ tc = tcase_create("mult-s16");
+ tcase_add_test(tc, mult_s16_test);
+ tcase_set_timeout(tc, 120);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/once-test.c b/src/tests/once-test.c
new file mode 100644
index 0000000..cb56187
--- /dev/null
+++ b/src/tests/once-test.c
@@ -0,0 +1,149 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef HAVE_PTHREAD
+#include <pthread.h>
+#ifdef HAVE_PTHREAD_SETAFFINITY_NP
+#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+#ifdef __FreeBSD__
+#include <pthread_np.h>
+#endif
+#include <sys/param.h>
+#include <sys/cpuset.h>
+#endif
+#endif
+#endif
+
+#include <check.h>
+
+#include <pulsecore/thread.h>
+#include <pulsecore/once.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/atomic.h>
+#include <pulse/xmalloc.h>
+
+static pa_once once = PA_ONCE_INIT;
+static volatile unsigned n_run = 0;
+static const char * volatile ran_by = NULL;
+#ifdef HAVE_PTHREAD
+static pthread_barrier_t barrier;
+#endif
+static unsigned n_cpu;
+
+#define N_ITERATIONS 500
+#define N_THREADS 100
+
+static void once_func(void) {
+ n_run++;
+ ran_by = (const char*) pa_thread_get_data(pa_thread_self());
+}
+
+static void thread_func(void *data) {
+#ifdef HAVE_PTHREAD
+ int r;
+
+#ifdef HAVE_PTHREAD_SETAFFINITY_NP
+ static pa_atomic_t i_cpu = PA_ATOMIC_INIT(0);
+#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+ cpuset_t mask;
+#else
+ cpu_set_t mask;
+#endif
+
+ CPU_ZERO(&mask);
+ CPU_SET((size_t) (pa_atomic_inc(&i_cpu) % n_cpu), &mask);
+ fail_unless(pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask) == 0);
+#endif
+
+ pa_log_debug("started up: %s", (char *) data);
+
+ r = pthread_barrier_wait(&barrier);
+ fail_unless(r == 0 || r == PTHREAD_BARRIER_SERIAL_THREAD);
+#endif /* HAVE_PTHREAD */
+
+ pa_run_once(&once, once_func);
+}
+
+START_TEST (once_test) {
+ unsigned n, i;
+
+ n_cpu = pa_ncpus();
+
+ for (n = 0; n < N_ITERATIONS; n++) {
+ pa_thread* threads[N_THREADS];
+
+#ifdef HAVE_PTHREAD
+ fail_unless(pthread_barrier_init(&barrier, NULL, N_THREADS) == 0);
+#endif
+
+ /* Yes, kinda ugly */
+ pa_zero(once);
+
+ for (i = 0; i < N_THREADS; i++)
+ threads[i] = pa_thread_new("once", thread_func, pa_sprintf_malloc("Thread #%i", i+1));
+
+ for (i = 0; i < N_THREADS; i++)
+ pa_thread_join(threads[i]);
+
+ fail_unless(n_run == 1);
+ pa_log_info("ran by %s", ran_by);
+
+ for (i = 0; i < N_THREADS; i++) {
+ pa_xfree(pa_thread_get_data(threads[i]));
+ pa_thread_free(threads[i]);
+ }
+
+ n_run = 0;
+ ran_by = NULL;
+
+#ifdef HAVE_PTHREAD
+ fail_unless(pthread_barrier_destroy(&barrier) == 0);
+#endif
+ }
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ if (!getenv("MAKE_CHECK"))
+ pa_log_set_level(PA_LOG_DEBUG);
+
+ s = suite_create("Once");
+ tc = tcase_create("once");
+ tcase_add_test(tc, once_test);
+ /* the default timeout is too small,
+ * set it to a reasonable large one.
+ */
+ tcase_set_timeout(tc, 60 * 60);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/pacat-simple.c b/src/tests/pacat-simple.c
new file mode 100644
index 0000000..113ff95
--- /dev/null
+++ b/src/tests/pacat-simple.c
@@ -0,0 +1,114 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <pulse/simple.h>
+#include <pulse/error.h>
+
+#define BUFSIZE 1024
+
+int main(int argc, char*argv[]) {
+
+ /* The Sample format to use */
+ static const pa_sample_spec ss = {
+ .format = PA_SAMPLE_S16LE,
+ .rate = 44100,
+ .channels = 2
+ };
+
+ pa_simple *s = NULL;
+ int ret = 1;
+ int error;
+
+ /* replace STDIN with the specified file if needed */
+ if (argc > 1) {
+ int fd;
+
+ if ((fd = open(argv[1], O_RDONLY)) < 0) {
+ fprintf(stderr, __FILE__": open() failed: %s\n", strerror(errno));
+ goto finish;
+ }
+
+ if (dup2(fd, STDIN_FILENO) < 0) {
+ fprintf(stderr, __FILE__": dup2() failed: %s\n", strerror(errno));
+ goto finish;
+ }
+
+ close(fd);
+ }
+
+ /* Create a new playback stream */
+ if (!(s = pa_simple_new(NULL, argv[0], PA_STREAM_PLAYBACK, NULL, "playback", &ss, NULL, NULL, &error))) {
+ fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error));
+ goto finish;
+ }
+
+ for (;;) {
+ uint8_t buf[BUFSIZE];
+ ssize_t r;
+
+#if 0
+ pa_usec_t latency;
+
+ if ((latency = pa_simple_get_latency(s, &error)) == (pa_usec_t) -1) {
+ fprintf(stderr, __FILE__": pa_simple_get_latency() failed: %s\n", pa_strerror(error));
+ goto finish;
+ }
+
+ fprintf(stderr, "%0.0f usec \r", (float)latency);
+#endif
+
+ /* Read some data ... */
+ if ((r = read(STDIN_FILENO, buf, sizeof(buf))) <= 0) {
+ if (r == 0) /* EOF */
+ break;
+
+ fprintf(stderr, __FILE__": read() failed: %s\n", strerror(errno));
+ goto finish;
+ }
+
+ /* ... and play it */
+ if (pa_simple_write(s, buf, (size_t) r, &error) < 0) {
+ fprintf(stderr, __FILE__": pa_simple_write() failed: %s\n", pa_strerror(error));
+ goto finish;
+ }
+ }
+
+ /* Make sure that every single sample was played */
+ if (pa_simple_drain(s, &error) < 0) {
+ fprintf(stderr, __FILE__": pa_simple_drain() failed: %s\n", pa_strerror(error));
+ goto finish;
+ }
+
+ ret = 0;
+
+finish:
+
+ if (s)
+ pa_simple_free(s);
+
+ return ret;
+}
diff --git a/src/tests/parec-simple.c b/src/tests/parec-simple.c
new file mode 100644
index 0000000..338a0e8
--- /dev/null
+++ b/src/tests/parec-simple.c
@@ -0,0 +1,94 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include <pulse/simple.h>
+#include <pulse/error.h>
+
+#define BUFSIZE 1024
+
+/* A simple routine calling UNIX write() in a loop */
+static ssize_t loop_write(int fd, const void*data, size_t size) {
+ ssize_t ret = 0;
+
+ while (size > 0) {
+ ssize_t r;
+
+ if ((r = write(fd, data, size)) < 0)
+ return r;
+
+ if (r == 0)
+ break;
+
+ ret += r;
+ data = (const uint8_t*) data + r;
+ size -= (size_t) r;
+ }
+
+ return ret;
+}
+
+int main(int argc, char*argv[]) {
+ /* The sample type to use */
+ static const pa_sample_spec ss = {
+ .format = PA_SAMPLE_S16LE,
+ .rate = 44100,
+ .channels = 2
+ };
+ pa_simple *s = NULL;
+ int ret = 1;
+ int error;
+
+ /* Create the recording stream */
+ if (!(s = pa_simple_new(NULL, argv[0], PA_STREAM_RECORD, NULL, "record", &ss, NULL, NULL, &error))) {
+ fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error));
+ goto finish;
+ }
+
+ for (;;) {
+ uint8_t buf[BUFSIZE];
+
+ /* Record some data ... */
+ if (pa_simple_read(s, buf, sizeof(buf), &error) < 0) {
+ fprintf(stderr, __FILE__": pa_simple_read() failed: %s\n", pa_strerror(error));
+ goto finish;
+ }
+
+ /* And write it to STDOUT */
+ if (loop_write(STDOUT_FILENO, buf, sizeof(buf)) != sizeof(buf)) {
+ fprintf(stderr, __FILE__": write() failed: %s\n", strerror(errno));
+ goto finish;
+ }
+ }
+
+ ret = 0;
+
+finish:
+
+ if (s)
+ pa_simple_free(s);
+
+ return ret;
+}
diff --git a/src/tests/passthrough-test.c b/src/tests/passthrough-test.c
new file mode 100644
index 0000000..4a1ef78
--- /dev/null
+++ b/src/tests/passthrough-test.c
@@ -0,0 +1,347 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdbool.h>
+
+#include <check.h>
+
+#include <pulse/pulseaudio.h>
+
+#include <pulsecore/core-util.h>
+
+#define SINK_NAME "passthrough-test"
+
+#define RATE 48000
+#define CHANNELS 6
+
+#define WAIT_FOR_OPERATION(o) \
+ do { \
+ while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) { \
+ pa_threaded_mainloop_wait(mainloop); \
+ } \
+ \
+ fail_unless(pa_operation_get_state(o) == PA_OPERATION_DONE); \
+ pa_operation_unref(o); \
+ } while (false)
+
+static pa_threaded_mainloop *mainloop = NULL;
+static pa_context *context = NULL;
+static pa_mainloop_api *mainloop_api = NULL;
+static uint32_t module_idx = PA_INVALID_INDEX;
+static int sink_num = 0;
+static char sink_name[256] = { 0, };
+static const char *bname = NULL;
+
+/* This is called whenever the context status changes */
+static void context_state_callback(pa_context *c, void *userdata) {
+ fail_unless(c != NULL);
+
+ switch (pa_context_get_state(c)) {
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+
+ case PA_CONTEXT_READY:
+ fprintf(stderr, "Connection established.\n");
+ pa_threaded_mainloop_signal(mainloop, false);
+ break;
+
+ case PA_CONTEXT_TERMINATED:
+ mainloop_api->quit(mainloop_api, 0);
+ pa_threaded_mainloop_signal(mainloop, false);
+ break;
+
+ case PA_CONTEXT_FAILED:
+ mainloop_api->quit(mainloop_api, 0);
+ pa_threaded_mainloop_signal(mainloop, false);
+ fprintf(stderr, "Context error: %s\n", pa_strerror(pa_context_errno(c)));
+ fail();
+ break;
+
+ default:
+ fail();
+ }
+}
+
+static void module_index_cb(pa_context *c, uint32_t idx, void *userdata) {
+ fail_unless(idx != PA_INVALID_INDEX);
+
+ module_idx = idx;
+
+ pa_threaded_mainloop_signal(mainloop, false);
+}
+
+static void success_cb(pa_context *c, int success, void *userdata) {
+ fail_unless(success != 0);
+
+ pa_threaded_mainloop_signal(mainloop, false);
+}
+
+static void passthrough_teardown() {
+ pa_operation *o;
+
+ pa_threaded_mainloop_lock(mainloop);
+
+ if (module_idx != PA_INVALID_INDEX) {
+ o = pa_context_unload_module(context, module_idx, success_cb, NULL);
+ WAIT_FOR_OPERATION(o);
+ }
+
+ pa_context_disconnect(context);
+ pa_context_unref(context);
+
+ pa_threaded_mainloop_unlock(mainloop);
+
+ pa_threaded_mainloop_stop(mainloop);
+ pa_threaded_mainloop_free(mainloop);
+}
+
+static void passthrough_setup() {
+ char modargs[128];
+ pa_operation *o;
+ int r;
+
+ /* Set up a new main loop */
+ mainloop = pa_threaded_mainloop_new();
+ fail_unless(mainloop != NULL);
+
+ mainloop_api = pa_threaded_mainloop_get_api(mainloop);
+
+ pa_threaded_mainloop_lock(mainloop);
+
+ pa_threaded_mainloop_start(mainloop);
+
+ context = pa_context_new(mainloop_api, bname);
+ fail_unless(context != NULL);
+
+ pa_context_set_state_callback(context, context_state_callback, NULL);
+
+ /* Connect the context */
+ r = pa_context_connect(context, NULL, 0, NULL);
+ fail_unless(r == 0);
+
+ pa_threaded_mainloop_wait(mainloop);
+
+ fail_unless(pa_context_get_state(context) == PA_CONTEXT_READY);
+
+ pa_snprintf(sink_name, sizeof(sink_name), "%s-%d", SINK_NAME, sink_num);
+ pa_snprintf(modargs, sizeof(modargs), "sink_name='%s' formats='ac3-iec61937, format.rate=\"[32000, 44100, 48000]\" format.channels=\"6\"; pcm'", sink_name);
+
+ o = pa_context_load_module(context, "module-null-sink", modargs, module_index_cb, NULL);
+ WAIT_FOR_OPERATION(o);
+
+ pa_threaded_mainloop_unlock(mainloop);
+
+ return;
+}
+
+static void nop_free_cb(void *p) {}
+
+static void underflow_cb(struct pa_stream *s, void *userdata) {
+ fprintf(stderr, "Stream finished\n");
+ pa_threaded_mainloop_signal(mainloop, false);
+}
+
+/* This routine is called whenever the stream state changes */
+static void stream_state_callback(pa_stream *s, void *userdata) {
+ /* We fill in fake AC3 data in terms of the corresponding PCM sample spec (S16LE, 2ch, at the given rate) */
+ int16_t data[RATE * 2] = { 0, }; /* one second space */
+
+ fail_unless(s != NULL);
+
+ switch (pa_stream_get_state(s)) {
+ case PA_STREAM_UNCONNECTED:
+ case PA_STREAM_CREATING:
+ break;
+
+ case PA_STREAM_TERMINATED:
+ pa_threaded_mainloop_signal(mainloop, false);
+ break;
+
+ case PA_STREAM_READY: {
+ int r;
+
+ r = pa_stream_write(s, data, sizeof(data), nop_free_cb, 0, PA_SEEK_ABSOLUTE);
+ fail_unless(r == 0);
+
+ /* Be notified when this stream is drained */
+ pa_stream_set_underflow_callback(s, underflow_cb, userdata);
+
+ pa_threaded_mainloop_signal(mainloop, false);
+ break;
+ }
+
+ case PA_STREAM_FAILED:
+ fprintf(stderr, "Stream error: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
+ pa_threaded_mainloop_signal(mainloop, false);
+ break;
+
+ default:
+ fail();
+ }
+}
+
+static pa_stream* connect_stream() {
+ int r;
+ pa_stream *s;
+ pa_format_info *formats[1];
+
+ pa_threaded_mainloop_lock(mainloop);
+
+ formats[0] = pa_format_info_new();
+ formats[0]->encoding = PA_ENCODING_AC3_IEC61937;
+ /* We set rate and channels to test that negotiation actually works. This
+ * must correspond to the rate and channels we configure module-null-sink
+ * for above. */
+ pa_format_info_set_rate(formats[0], RATE);
+ pa_format_info_set_channels(formats[0], CHANNELS);
+
+ s = pa_stream_new_extended(context, "passthrough test", formats, 1, NULL);
+ fail_unless(s != NULL);
+
+ pa_stream_set_state_callback(s, stream_state_callback, NULL);
+ r = pa_stream_connect_playback(s, sink_name, NULL, PA_STREAM_NOFLAGS, NULL, NULL);
+
+ fail_unless(r == 0);
+
+ pa_threaded_mainloop_wait(mainloop);
+
+ fail_unless(pa_stream_get_state(s) == PA_STREAM_READY);
+
+ pa_threaded_mainloop_unlock(mainloop);
+
+ return s;
+}
+
+static void disconnect_stream(pa_stream *s) {
+ int r;
+
+ pa_threaded_mainloop_lock(mainloop);
+
+ r = pa_stream_disconnect(s);
+ fail_unless(r == 0);
+
+ pa_threaded_mainloop_wait(mainloop);
+ fail_unless(pa_stream_get_state(s) == PA_STREAM_TERMINATED);
+
+ pa_stream_unref(s);
+
+ pa_threaded_mainloop_unlock(mainloop);
+}
+
+START_TEST (passthrough_playback_test) {
+ /* Create a passthrough stream, and make sure format negotiation actually
+ * works */
+ pa_stream *stream;
+
+ stream = connect_stream();
+
+ /* Wait for underflow_cb() */
+ pa_threaded_mainloop_lock(mainloop);
+ pa_threaded_mainloop_wait(mainloop);
+ fail_unless(pa_stream_get_state(stream) == PA_STREAM_READY);
+ pa_threaded_mainloop_unlock(mainloop);
+
+ disconnect_stream(stream);
+}
+END_TEST
+
+static void sink_info_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) {
+ pa_cvolume *v = (pa_cvolume *) userdata;
+
+ if (eol)
+ return;
+
+ *v = i->volume;
+
+ pa_threaded_mainloop_signal(mainloop, false);
+}
+
+static void get_sink_volume(pa_cvolume *v) {
+ pa_operation *o;
+
+ pa_threaded_mainloop_lock(mainloop);
+
+ o = pa_context_get_sink_info_by_name(context, sink_name, sink_info_cb, v);
+ WAIT_FOR_OPERATION(o);
+
+ pa_threaded_mainloop_unlock(mainloop);
+}
+
+START_TEST (passthrough_volume_test) {
+ /* Set a non-100% volume of the sink before playback, create a passthrough
+ * stream, make sure volume gets set to 100%, and then restored when the
+ * stream goes away */
+ pa_stream *stream;
+ pa_operation *o;
+ pa_cvolume volume, tmp;
+
+ pa_threaded_mainloop_lock(mainloop);
+
+ pa_cvolume_set(&volume, 2, PA_VOLUME_NORM / 2);
+ o = pa_context_set_sink_volume_by_name(context, sink_name, &volume, success_cb, NULL);
+ WAIT_FOR_OPERATION(o);
+
+ pa_threaded_mainloop_unlock(mainloop);
+
+ stream = connect_stream();
+
+ pa_threaded_mainloop_lock(mainloop);
+ pa_threaded_mainloop_wait(mainloop);
+ fail_unless(PA_STREAM_IS_GOOD(pa_stream_get_state(stream)));
+ pa_threaded_mainloop_unlock(mainloop);
+
+ get_sink_volume(&tmp);
+ fail_unless(pa_cvolume_is_norm(&tmp));
+
+ disconnect_stream(stream);
+
+ get_sink_volume(&tmp);
+ fail_unless(pa_cvolume_equal(&volume, &tmp));
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ bname = argv[0];
+
+ s = suite_create("Passthrough");
+ tc = tcase_create("passthrough");
+ tcase_add_checked_fixture(tc, passthrough_setup, passthrough_teardown);
+ tcase_add_test(tc, passthrough_playback_test);
+ sink_num++;
+ tcase_add_test(tc, passthrough_volume_test);
+ tcase_set_timeout(tc, 5);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/proplist-test.c b/src/tests/proplist-test.c
new file mode 100644
index 0000000..a0074f8
--- /dev/null
+++ b/src/tests/proplist-test.c
@@ -0,0 +1,119 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include <check.h>
+
+#include <pulse/proplist.h>
+#include <pulse/xmalloc.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/modargs.h>
+
+START_TEST (proplist_test) {
+ pa_modargs *ma;
+ pa_proplist *a, *b, *c, *d;
+ char *s, *t, *u, *v;
+ const char *text;
+ const char *x[] = { "foo", NULL };
+
+ a = pa_proplist_new();
+ fail_unless(pa_proplist_sets(a, PA_PROP_MEDIA_TITLE, "Brandenburgische Konzerte") == 0);
+ fail_unless(pa_proplist_sets(a, PA_PROP_MEDIA_ARTIST, "Johann Sebastian Bach") == 0);
+
+ b = pa_proplist_new();
+ fail_unless(pa_proplist_sets(b, PA_PROP_MEDIA_TITLE, "Goldbergvariationen") == 0);
+ fail_unless(pa_proplist_set(b, PA_PROP_MEDIA_ICON, "\0\1\2\3\4\5\6\7", 8) == 0);
+
+ pa_proplist_update(a, PA_UPDATE_MERGE, b);
+
+ fail_unless(!pa_proplist_gets(a, PA_PROP_MEDIA_ICON));
+
+ pa_log_debug("%s", pa_strnull(pa_proplist_gets(a, PA_PROP_MEDIA_TITLE)));
+ fail_unless(pa_proplist_unset(b, PA_PROP_MEDIA_TITLE) == 0);
+
+ s = pa_proplist_to_string(a);
+ t = pa_proplist_to_string(b);
+ pa_log_debug("---\n%s---\n%s", s, t);
+
+ c = pa_proplist_from_string(s);
+ u = pa_proplist_to_string(c);
+ fail_unless(pa_streq(s, u));
+
+ pa_xfree(s);
+ pa_xfree(t);
+ pa_xfree(u);
+
+ pa_proplist_free(a);
+ pa_proplist_free(b);
+ pa_proplist_free(c);
+
+ text = " eins = zwei drei = \"\\\"vier\\\"\" fuenf=sechs sieben ='\\a\\c\\h\\t\\'\\\"' neun= hex:0123456789abCDef ";
+
+ pa_log_debug("%s", text);
+ d = pa_proplist_from_string(text);
+ v = pa_proplist_to_string(d);
+ pa_proplist_free(d);
+ pa_log_debug("%s", v);
+ d = pa_proplist_from_string(v);
+ pa_xfree(v);
+ v = pa_proplist_to_string(d);
+ pa_proplist_free(d);
+ pa_log_debug("%s", v);
+ pa_xfree(v);
+
+ ma = pa_modargs_new("foo='foobar=waldo foo2=\"lj\\\"dhflh\" foo3=\"kjlskj\\'\"'", x);
+ fail_unless(ma != NULL);
+ a = pa_proplist_new();
+ fail_unless(a != NULL);
+
+ fail_unless(pa_modargs_get_proplist(ma, "foo", a, PA_UPDATE_REPLACE) >= 0);
+
+ pa_log_debug("%s", v = pa_proplist_to_string(a));
+ pa_xfree(v);
+
+ pa_proplist_free(a);
+ pa_modargs_free(ma);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ if (!getenv("MAKE_CHECK"))
+ pa_log_set_level(PA_LOG_DEBUG);
+
+ s = suite_create("Property List");
+ tc = tcase_create("propertylist");
+ tcase_add_test(tc, proplist_test);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/queue-test.c b/src/tests/queue-test.c
new file mode 100644
index 0000000..dfdac63
--- /dev/null
+++ b/src/tests/queue-test.c
@@ -0,0 +1,83 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <check.h>
+
+#include <pulsecore/queue.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+START_TEST (queue_test) {
+ pa_queue *q;
+
+ q = pa_queue_new();
+ fail_unless(q != NULL);
+
+ fail_unless(pa_queue_isempty(q));
+
+ pa_queue_push(q, (void*) "eins");
+ pa_log("%s\n", (char*) pa_queue_pop(q));
+
+ fail_unless(pa_queue_isempty(q));
+
+ pa_queue_push(q, (void*) "zwei");
+ pa_queue_push(q, (void*) "drei");
+ pa_queue_push(q, (void*) "vier");
+
+ pa_log("%s\n", (char*) pa_queue_pop(q));
+ pa_log("%s\n", (char*) pa_queue_pop(q));
+
+ pa_queue_push(q, (void*) "fuenf");
+
+ pa_log("%s\n", (char*) pa_queue_pop(q));
+ pa_log("%s\n", (char*) pa_queue_pop(q));
+
+ fail_unless(pa_queue_isempty(q));
+
+ pa_queue_push(q, (void*) "sechs");
+ pa_queue_push(q, (void*) "sieben");
+
+ pa_queue_free(q, NULL);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ s = suite_create("Queue");
+ tc = tcase_create("queue");
+ tcase_add_test(tc, queue_test);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/remix-test.c b/src/tests/remix-test.c
new file mode 100644
index 0000000..8e1292d
--- /dev/null
+++ b/src/tests/remix-test.c
@@ -0,0 +1,110 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include <pulse/sample.h>
+
+#include <pulsecore/resampler.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/memblock.h>
+
+struct resample_flags {
+ const char *str;
+ pa_resample_flags_t value;
+};
+
+/* Call like this to get an initializer for struct resample_flags:
+ * RESAMPLE_FLAGS(PA_RESAMPLER_PRODUCE_LFE)
+ */
+#define RESAMPLE_FLAGS(flags) { .str = #flags, .value = (flags) }
+
+
+int main(int argc, char *argv[]) {
+
+ static const pa_channel_map maps[] = {
+ { 1, { PA_CHANNEL_POSITION_MONO } },
+ { 2, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT } },
+ { 3, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_CENTER } },
+ { 3, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_LFE } },
+ { 3, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_REAR_CENTER } },
+ { 4, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_CENTER, PA_CHANNEL_POSITION_LFE } },
+ { 4, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_CENTER, PA_CHANNEL_POSITION_REAR_CENTER } },
+ { 4, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT } },
+ { 5, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, PA_CHANNEL_POSITION_CENTER } },
+ { 5, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, PA_CHANNEL_POSITION_LFE } },
+ { 6, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, PA_CHANNEL_POSITION_LFE, PA_CHANNEL_POSITION_CENTER } },
+ { 8, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, PA_CHANNEL_POSITION_LFE, PA_CHANNEL_POSITION_CENTER, PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT } },
+ { 0, { 0 } }
+ };
+
+ static const struct resample_flags flag_sets[] = {
+ RESAMPLE_FLAGS(0),
+ RESAMPLE_FLAGS(PA_RESAMPLER_NO_REMAP),
+ RESAMPLE_FLAGS(PA_RESAMPLER_NO_REMIX),
+ RESAMPLE_FLAGS(PA_RESAMPLER_PRODUCE_LFE),
+ RESAMPLE_FLAGS(PA_RESAMPLER_NO_FILL_SINK),
+ RESAMPLE_FLAGS(PA_RESAMPLER_PRODUCE_LFE | PA_RESAMPLER_NO_FILL_SINK),
+ RESAMPLE_FLAGS(PA_RESAMPLER_CONSUME_LFE),
+ RESAMPLE_FLAGS(PA_RESAMPLER_CONSUME_LFE | PA_RESAMPLER_NO_FILL_SINK),
+ RESAMPLE_FLAGS(PA_RESAMPLER_PRODUCE_LFE | PA_RESAMPLER_CONSUME_LFE),
+ RESAMPLE_FLAGS(PA_RESAMPLER_PRODUCE_LFE | PA_RESAMPLER_CONSUME_LFE | PA_RESAMPLER_NO_FILL_SINK),
+ { .str = NULL, .value = 0 },
+ };
+
+ unsigned i, j, k;
+ pa_mempool *pool;
+ unsigned crossover_freq = 120;
+
+ pa_log_set_level(PA_LOG_DEBUG);
+
+ pa_assert_se(pool = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true));
+
+ for (i = 0; maps[i].channels > 0; i++)
+ for (j = 0; maps[j].channels > 0; j++) {
+ char a[PA_CHANNEL_MAP_SNPRINT_MAX], b[PA_CHANNEL_MAP_SNPRINT_MAX];
+ pa_resampler *r;
+ pa_sample_spec ss1, ss2;
+
+ ss1.channels = maps[i].channels;
+ ss2.channels = maps[j].channels;
+
+ ss1.rate = ss2.rate = 44100;
+ ss1.format = ss2.format = PA_SAMPLE_S16NE;
+
+ for (k = 0; flag_sets[k].str; k++) {
+ pa_log_info("Converting from '%s' to '%s' with flags %s.", pa_channel_map_snprint(a, sizeof(a), &maps[i]),
+ pa_channel_map_snprint(b, sizeof(b), &maps[j]), flag_sets[k].str);
+
+ r = pa_resampler_new(pool, &ss1, &maps[i], &ss2, &maps[j], crossover_freq, PA_RESAMPLER_AUTO,
+ flag_sets[k].value);
+
+ /* We don't really care for the resampler. We just want to
+ * see the remixing debug output. */
+
+ pa_resampler_free(r);
+ }
+ }
+
+ pa_mempool_unref(pool);
+
+ return 0;
+}
diff --git a/src/tests/resampler-test.c b/src/tests/resampler-test.c
new file mode 100644
index 0000000..40000d5
--- /dev/null
+++ b/src/tests/resampler-test.c
@@ -0,0 +1,479 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <getopt.h>
+#include <locale.h>
+
+#include <pulse/pulseaudio.h>
+
+#include <pulse/rtclock.h>
+#include <pulse/sample.h>
+#include <pulse/volume.h>
+
+#include <pulsecore/i18n.h>
+#include <pulsecore/log.h>
+#include <pulsecore/resampler.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/endianmacros.h>
+#include <pulsecore/memblock.h>
+#include <pulsecore/sample-util.h>
+#include <pulsecore/core-util.h>
+
+static void dump_block(const char *label, const pa_sample_spec *ss, const pa_memchunk *chunk) {
+ void *d;
+ unsigned i;
+
+ if (getenv("MAKE_CHECK"))
+ return;
+ printf("%s: \t", label);
+
+ d = pa_memblock_acquire(chunk->memblock);
+
+ switch (ss->format) {
+
+ case PA_SAMPLE_U8:
+ case PA_SAMPLE_ULAW:
+ case PA_SAMPLE_ALAW: {
+ uint8_t *u = d;
+
+ for (i = 0; i < chunk->length / pa_frame_size(ss); i++)
+ printf(" 0x%02x ", *(u++));
+
+ break;
+ }
+
+ case PA_SAMPLE_S16NE:
+ case PA_SAMPLE_S16RE: {
+ uint16_t *u = d;
+
+ for (i = 0; i < chunk->length / pa_frame_size(ss); i++)
+ printf(" 0x%04x ", *(u++));
+
+ break;
+ }
+
+ case PA_SAMPLE_S32NE:
+ case PA_SAMPLE_S32RE: {
+ uint32_t *u = d;
+
+ for (i = 0; i < chunk->length / pa_frame_size(ss); i++)
+ printf("0x%08x ", *(u++));
+
+ break;
+ }
+
+ case PA_SAMPLE_S24_32NE:
+ case PA_SAMPLE_S24_32RE: {
+ uint32_t *u = d;
+
+ for (i = 0; i < chunk->length / pa_frame_size(ss); i++)
+ printf("0x%08x ", *(u++));
+
+ break;
+ }
+
+ case PA_SAMPLE_FLOAT32NE:
+ case PA_SAMPLE_FLOAT32RE: {
+ float *u = d;
+
+ for (i = 0; i < chunk->length / pa_frame_size(ss); i++) {
+ printf("%4.3g ", ss->format == PA_SAMPLE_FLOAT32NE ? *u : PA_READ_FLOAT32RE(u));
+ u++;
+ }
+
+ break;
+ }
+
+ case PA_SAMPLE_S24LE:
+ case PA_SAMPLE_S24BE: {
+ uint8_t *u = d;
+
+ for (i = 0; i < chunk->length / pa_frame_size(ss); i++) {
+ printf(" 0x%06x ", PA_READ24NE(u));
+ u += pa_frame_size(ss);
+ }
+
+ break;
+ }
+
+ default:
+ pa_assert_not_reached();
+ }
+
+ printf("\n");
+
+ pa_memblock_release(chunk->memblock);
+}
+
+static pa_memblock* generate_block(pa_mempool *pool, const pa_sample_spec *ss) {
+ pa_memblock *r;
+ void *d;
+ unsigned i;
+
+ pa_assert_se(r = pa_memblock_new(pool, pa_frame_size(ss) * 10));
+ d = pa_memblock_acquire(r);
+
+ switch (ss->format) {
+
+ case PA_SAMPLE_U8:
+ case PA_SAMPLE_ULAW:
+ case PA_SAMPLE_ALAW: {
+ uint8_t *u = d;
+
+ u[0] = 0x00;
+ u[1] = 0xFF;
+ u[2] = 0x7F;
+ u[3] = 0x80;
+ u[4] = 0x9f;
+ u[5] = 0x3f;
+ u[6] = 0x1;
+ u[7] = 0xF0;
+ u[8] = 0x20;
+ u[9] = 0x21;
+ break;
+ }
+
+ case PA_SAMPLE_S16NE:
+ case PA_SAMPLE_S16RE: {
+ uint16_t *u = d;
+
+ u[0] = 0x0000;
+ u[1] = 0xFFFF;
+ u[2] = 0x7FFF;
+ u[3] = 0x8000;
+ u[4] = 0x9fff;
+ u[5] = 0x3fff;
+ u[6] = 0x1;
+ u[7] = 0xF000;
+ u[8] = 0x20;
+ u[9] = 0x21;
+ break;
+ }
+
+ case PA_SAMPLE_S32NE:
+ case PA_SAMPLE_S32RE: {
+ uint32_t *u = d;
+
+ u[0] = 0x00000001;
+ u[1] = 0xFFFF0002;
+ u[2] = 0x7FFF0003;
+ u[3] = 0x80000004;
+ u[4] = 0x9fff0005;
+ u[5] = 0x3fff0006;
+ u[6] = 0x10007;
+ u[7] = 0xF0000008;
+ u[8] = 0x200009;
+ u[9] = 0x21000A;
+ break;
+ }
+
+ case PA_SAMPLE_S24_32NE:
+ case PA_SAMPLE_S24_32RE: {
+ uint32_t *u = d;
+
+ u[0] = 0x000001;
+ u[1] = 0xFF0002;
+ u[2] = 0x7F0003;
+ u[3] = 0x800004;
+ u[4] = 0x9f0005;
+ u[5] = 0x3f0006;
+ u[6] = 0x107;
+ u[7] = 0xF00008;
+ u[8] = 0x2009;
+ u[9] = 0x210A;
+ break;
+ }
+
+ case PA_SAMPLE_FLOAT32NE:
+ case PA_SAMPLE_FLOAT32RE: {
+ float *u = d;
+
+ u[0] = 0.0f;
+ u[1] = -1.0f;
+ u[2] = 1.0f;
+ u[3] = 4711.0f;
+ u[4] = 0.222f;
+ u[5] = 0.33f;
+ u[6] = -.3f;
+ u[7] = 99.0f;
+ u[8] = -0.555f;
+ u[9] = -.123f;
+
+ if (ss->format == PA_SAMPLE_FLOAT32RE)
+ for (i = 0; i < 10; i++)
+ PA_WRITE_FLOAT32RE(&u[i], u[i]);
+
+ break;
+ }
+
+ case PA_SAMPLE_S24NE:
+ case PA_SAMPLE_S24RE: {
+ uint8_t *u = d;
+
+ PA_WRITE24NE(u, 0x000001);
+ PA_WRITE24NE(u+3, 0xFF0002);
+ PA_WRITE24NE(u+6, 0x7F0003);
+ PA_WRITE24NE(u+9, 0x800004);
+ PA_WRITE24NE(u+12, 0x9f0005);
+ PA_WRITE24NE(u+15, 0x3f0006);
+ PA_WRITE24NE(u+18, 0x107);
+ PA_WRITE24NE(u+21, 0xF00008);
+ PA_WRITE24NE(u+24, 0x2009);
+ PA_WRITE24NE(u+27, 0x210A);
+ break;
+ }
+
+ default:
+ pa_assert_not_reached();
+ }
+
+ pa_memblock_release(r);
+
+ return r;
+}
+
+static void help(const char *argv0) {
+ printf("%s [options]\n\n"
+ "-h, --help Show this help\n"
+ "-v, --verbose Print debug messages\n"
+ " --from-rate=SAMPLERATE From sample rate in Hz (defaults to 44100)\n"
+ " --from-format=SAMPLEFORMAT From sample type (defaults to s16le)\n"
+ " --from-channels=CHANNELS From number of channels (defaults to 1)\n"
+ " --to-rate=SAMPLERATE To sample rate in Hz (defaults to 44100)\n"
+ " --to-format=SAMPLEFORMAT To sample type (defaults to s16le)\n"
+ " --to-channels=CHANNELS To number of channels (defaults to 1)\n"
+ " --resample-method=METHOD Resample method (defaults to auto)\n"
+ " --seconds=SECONDS From stream duration (defaults to 60)\n"
+ "\n"
+ "If the formats are not specified, the test performs all formats combinations,\n"
+ "back and forth.\n"
+ "\n"
+ "Sample type must be one of s16le, s16be, u8, float32le, float32be, ulaw, alaw,\n"
+ "s24le, s24be, s24-32le, s24-32be, s32le, s32be (defaults to s16ne)\n"
+ "\n"
+ "See --dump-resample-methods for possible values of resample methods.\n",
+ argv0);
+}
+
+enum {
+ ARG_VERSION = 256,
+ ARG_FROM_SAMPLERATE,
+ ARG_FROM_SAMPLEFORMAT,
+ ARG_FROM_CHANNELS,
+ ARG_TO_SAMPLERATE,
+ ARG_TO_SAMPLEFORMAT,
+ ARG_TO_CHANNELS,
+ ARG_SECONDS,
+ ARG_RESAMPLE_METHOD,
+ ARG_DUMP_RESAMPLE_METHODS
+};
+
+static void dump_resample_methods(void) {
+ int i;
+
+ for (i = 0; i < PA_RESAMPLER_MAX; i++)
+ if (pa_resample_method_supported(i))
+ printf("%s\n", pa_resample_method_to_string(i));
+
+}
+
+int main(int argc, char *argv[]) {
+ pa_mempool *pool = NULL;
+ pa_sample_spec a, b;
+ int ret = 1, c;
+ bool all_formats = true;
+ pa_resample_method_t method;
+ int seconds;
+ unsigned crossover_freq = 120;
+
+ static const struct option long_options[] = {
+ {"help", 0, NULL, 'h'},
+ {"verbose", 0, NULL, 'v'},
+ {"version", 0, NULL, ARG_VERSION},
+ {"from-rate", 1, NULL, ARG_FROM_SAMPLERATE},
+ {"from-format", 1, NULL, ARG_FROM_SAMPLEFORMAT},
+ {"from-channels", 1, NULL, ARG_FROM_CHANNELS},
+ {"to-rate", 1, NULL, ARG_TO_SAMPLERATE},
+ {"to-format", 1, NULL, ARG_TO_SAMPLEFORMAT},
+ {"to-channels", 1, NULL, ARG_TO_CHANNELS},
+ {"seconds", 1, NULL, ARG_SECONDS},
+ {"resample-method", 1, NULL, ARG_RESAMPLE_METHOD},
+ {"dump-resample-methods", 0, NULL, ARG_DUMP_RESAMPLE_METHODS},
+ {NULL, 0, NULL, 0}
+ };
+
+ setlocale(LC_ALL, "");
+#ifdef ENABLE_NLS
+ bindtextdomain(GETTEXT_PACKAGE, PULSE_LOCALEDIR);
+#endif
+
+ pa_log_set_level(PA_LOG_WARN);
+ if (!getenv("MAKE_CHECK"))
+ pa_log_set_level(PA_LOG_INFO);
+
+ a.channels = b.channels = 1;
+ a.rate = b.rate = 44100;
+ a.format = b.format = PA_SAMPLE_S16LE;
+
+ method = PA_RESAMPLER_AUTO;
+ seconds = 60;
+
+ while ((c = getopt_long(argc, argv, "hv", long_options, NULL)) != -1) {
+
+ switch (c) {
+ case 'h' :
+ help(argv[0]);
+ ret = 0;
+ goto quit;
+
+ case 'v':
+ pa_log_set_level(PA_LOG_DEBUG);
+ break;
+
+ case ARG_VERSION:
+ printf("%s %s\n", argv[0], PACKAGE_VERSION);
+ ret = 0;
+ goto quit;
+
+ case ARG_DUMP_RESAMPLE_METHODS:
+ dump_resample_methods();
+ ret = 0;
+ goto quit;
+
+ case ARG_FROM_CHANNELS:
+ a.channels = (uint8_t) atoi(optarg);
+ break;
+
+ case ARG_FROM_SAMPLEFORMAT:
+ a.format = pa_parse_sample_format(optarg);
+ all_formats = false;
+ break;
+
+ case ARG_FROM_SAMPLERATE:
+ a.rate = (uint32_t) atoi(optarg);
+ break;
+
+ case ARG_TO_CHANNELS:
+ b.channels = (uint8_t) atoi(optarg);
+ break;
+
+ case ARG_TO_SAMPLEFORMAT:
+ b.format = pa_parse_sample_format(optarg);
+ all_formats = false;
+ break;
+
+ case ARG_TO_SAMPLERATE:
+ b.rate = (uint32_t) atoi(optarg);
+ break;
+
+ case ARG_SECONDS:
+ seconds = atoi(optarg);
+ break;
+
+ case ARG_RESAMPLE_METHOD:
+ if (*optarg == '\0' || pa_streq(optarg, "help")) {
+ dump_resample_methods();
+ ret = 0;
+ goto quit;
+ }
+ method = pa_parse_resample_method(optarg);
+ break;
+
+ default:
+ goto quit;
+ }
+ }
+
+ ret = 0;
+ pa_assert_se(pool = pa_mempool_new(PA_MEM_TYPE_PRIVATE, 0, true));
+
+ if (!all_formats) {
+
+ pa_resampler *resampler;
+ pa_memchunk i, j;
+ pa_usec_t ts;
+
+ pa_log_debug("Compilation CFLAGS: %s", PA_CFLAGS);
+ pa_log_debug("=== %d seconds: %d Hz %d ch (%s) -> %d Hz %d ch (%s)", seconds,
+ a.rate, a.channels, pa_sample_format_to_string(a.format),
+ b.rate, b.channels, pa_sample_format_to_string(b.format));
+
+ ts = pa_rtclock_now();
+ pa_assert_se(resampler = pa_resampler_new(pool, &a, NULL, &b, NULL, crossover_freq, method, 0));
+ pa_log_info("init: %llu", (long long unsigned)(pa_rtclock_now() - ts));
+
+ i.memblock = pa_memblock_new(pool, pa_usec_to_bytes(1*PA_USEC_PER_SEC, &a));
+
+ ts = pa_rtclock_now();
+ i.length = pa_memblock_get_length(i.memblock);
+ i.index = 0;
+ while (seconds--) {
+ pa_resampler_run(resampler, &i, &j);
+ if (j.memblock)
+ pa_memblock_unref(j.memblock);
+ }
+ pa_log_info("resampling: %llu", (long long unsigned)(pa_rtclock_now() - ts));
+ pa_memblock_unref(i.memblock);
+
+ pa_resampler_free(resampler);
+
+ goto quit;
+ }
+
+ for (a.format = 0; a.format < PA_SAMPLE_MAX; a.format ++) {
+ for (b.format = 0; b.format < PA_SAMPLE_MAX; b.format ++) {
+ pa_resampler *forth, *back;
+ pa_memchunk i, j, k;
+
+ pa_log_debug("=== %s -> %s -> %s -> /2",
+ pa_sample_format_to_string(a.format),
+ pa_sample_format_to_string(b.format),
+ pa_sample_format_to_string(a.format));
+
+ pa_assert_se(forth = pa_resampler_new(pool, &a, NULL, &b, NULL, crossover_freq, method, 0));
+ pa_assert_se(back = pa_resampler_new(pool, &b, NULL, &a, NULL, crossover_freq, method, 0));
+
+ i.memblock = generate_block(pool, &a);
+ i.length = pa_memblock_get_length(i.memblock);
+ i.index = 0;
+ pa_resampler_run(forth, &i, &j);
+ pa_resampler_run(back, &j, &k);
+
+ dump_block("before", &a, &i);
+ dump_block("after", &b, &j);
+ dump_block("reverse", &a, &k);
+
+ pa_memblock_unref(i.memblock);
+ pa_memblock_unref(j.memblock);
+ pa_memblock_unref(k.memblock);
+
+ pa_resampler_free(forth);
+ pa_resampler_free(back);
+ }
+ }
+
+ quit:
+ if (pool)
+ pa_mempool_unref(pool);
+
+ return ret;
+}
diff --git a/src/tests/rtpoll-test.c b/src/tests/rtpoll-test.c
new file mode 100644
index 0000000..aab637b
--- /dev/null
+++ b/src/tests/rtpoll-test.c
@@ -0,0 +1,106 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <check.h>
+#include <signal.h>
+
+#include <pulsecore/poll.h>
+#include <pulsecore/log.h>
+#include <pulsecore/rtpoll.h>
+
+static int before(pa_rtpoll_item *i) {
+ pa_log("before");
+ return 0;
+}
+
+static void after(pa_rtpoll_item *i) {
+ pa_log("after");
+}
+
+static int worker(pa_rtpoll_item *w) {
+ pa_log("worker");
+ return 0;
+}
+
+START_TEST (rtpoll_test) {
+ pa_rtpoll *p;
+ pa_rtpoll_item *i, *w;
+ struct pollfd *pollfd;
+
+ p = pa_rtpoll_new();
+
+ i = pa_rtpoll_item_new(p, PA_RTPOLL_EARLY, 1);
+ pa_rtpoll_item_set_before_callback(i, before, NULL);
+ pa_rtpoll_item_set_after_callback(i, after, NULL);
+
+ pollfd = pa_rtpoll_item_get_pollfd(i, NULL);
+ pollfd->fd = 0;
+ pollfd->events = POLLIN;
+
+ w = pa_rtpoll_item_new(p, PA_RTPOLL_NORMAL, 0);
+ pa_rtpoll_item_set_before_callback(w, worker, NULL);
+
+ pa_rtpoll_set_timer_relative(p, 10000000); /* 10 s */
+
+ fail_unless(pa_rtpoll_run(p) >= 0);
+
+ pa_rtpoll_item_free(i);
+
+ i = pa_rtpoll_item_new(p, PA_RTPOLL_EARLY, 1);
+ pa_rtpoll_item_set_before_callback(i, before, NULL);
+ pa_rtpoll_item_set_after_callback(i, after, NULL);
+
+ pollfd = pa_rtpoll_item_get_pollfd(i, NULL);
+ pollfd->fd = 0;
+ pollfd->events = POLLIN;
+
+ fail_unless(pa_rtpoll_run(p) >= 0);
+
+ pa_rtpoll_item_free(i);
+
+ pa_rtpoll_item_free(w);
+
+ pa_rtpoll_free(p);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ s = suite_create("RT Poll");
+ tc = tcase_create("rtpoll");
+ tcase_add_test(tc, rtpoll_test);
+ /* the default timeout is too small,
+ * set it to a reasonable large one.
+ */
+ tcase_set_timeout(tc, 60 * 60);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/rtstutter.c b/src/tests/rtstutter.c
new file mode 100644
index 0000000..56b5146
--- /dev/null
+++ b/src/tests/rtstutter.c
@@ -0,0 +1,128 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2008 Lennart Poettering
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <inttypes.h>
+
+#ifdef HAVE_PTHREAD
+#include <pthread.h>
+#ifdef HAVE_PTHREAD_SETAFFINITY_NP
+#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+#ifdef __FreeBSD__
+#include <pthread_np.h>
+#endif
+#include <sys/param.h>
+#include <sys/cpuset.h>
+#endif
+#endif
+#endif
+
+#include <pulse/util.h>
+#include <pulse/timeval.h>
+#include <pulse/gccmacro.h>
+
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/core-rtclock.h>
+
+static int msec_lower, msec_upper;
+
+static void work(void *p) PA_GCC_NORETURN;
+
+static void work(void *p) {
+
+ pa_log_notice("CPU%i: Created thread.", PA_PTR_TO_UINT(p));
+
+ pa_thread_make_realtime(12);
+
+#ifdef HAVE_PTHREAD_SETAFFINITY_NP
+{
+#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+ cpuset_t mask;
+#else
+ cpu_set_t mask;
+#endif
+
+ CPU_ZERO(&mask);
+ CPU_SET((size_t) PA_PTR_TO_UINT(p), &mask);
+ pa_assert_se(pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask) == 0);
+}
+#endif
+
+ for (;;) {
+ struct timeval now, end;
+ uint64_t usec;
+
+ pa_log_notice("CPU%i: Sleeping for 1s", PA_PTR_TO_UINT(p));
+ pa_msleep(1000);
+
+ usec =
+ (uint64_t) ((((double) rand())*(double)(msec_upper-msec_lower)*PA_USEC_PER_MSEC)/RAND_MAX) +
+ (uint64_t) ((uint64_t) msec_lower*PA_USEC_PER_MSEC);
+
+ pa_log_notice("CPU%i: Freezing for %ims", PA_PTR_TO_UINT(p), (int) (usec/PA_USEC_PER_MSEC));
+
+ pa_rtclock_get(&end);
+ pa_timeval_add(&end, usec);
+
+ do {
+ pa_rtclock_get(&now);
+ } while (pa_timeval_cmp(&now, &end) < 0);
+ }
+}
+
+int main(int argc, char*argv[]) {
+ unsigned n;
+
+ pa_log_set_level(PA_LOG_INFO);
+
+ srand((unsigned) time(NULL));
+
+ if (argc >= 3) {
+ msec_lower = atoi(argv[1]);
+ msec_upper = atoi(argv[2]);
+ } else if (argc >= 2) {
+ msec_lower = 0;
+ msec_upper = atoi(argv[1]);
+ } else {
+ msec_lower = 0;
+ msec_upper = 1000;
+ }
+
+ pa_assert(msec_upper > 0);
+ pa_assert(msec_upper >= msec_lower);
+
+ pa_log_notice("Creating random latencies in the range of %ims to %ims.", msec_lower, msec_upper);
+
+ for (n = 1; n < pa_ncpus(); n++) {
+ pa_assert_se(pa_thread_new("rtstutter", work, PA_UINT_TO_PTR(n)));
+ }
+
+ work(PA_INT_TO_PTR(0));
+
+ return 0;
+}
diff --git a/src/tests/runtime-test-util.h b/src/tests/runtime-test-util.h
new file mode 100644
index 0000000..7d3443a
--- /dev/null
+++ b/src/tests/runtime-test-util.h
@@ -0,0 +1,56 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+***/
+
+#ifndef fooruntimetestutilhfoo
+#define fooruntimetestutilhfoo
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <math.h>
+
+#include <pulsecore/macro.h>
+#include <pulse/rtclock.h>
+
+#define PA_RUNTIME_TEST_RUN_START(l, t1, t2) \
+{ \
+ int _j, _k; \
+ int _times = (t1), _times2 = (t2); \
+ pa_usec_t _start, _stop; \
+ pa_usec_t _min = INT_MAX, _max = 0; \
+ double _s1 = 0, _s2 = 0; \
+ const char *_label = (l); \
+ \
+ for (_k = 0; _k < _times2; _k++) { \
+ _start = pa_rtclock_now(); \
+ for (_j = 0; _j < _times; _j++)
+
+#define PA_RUNTIME_TEST_RUN_STOP \
+ _stop = pa_rtclock_now(); \
+ \
+ if (_min > (_stop - _start)) _min = _stop - _start; \
+ if (_max < (_stop - _start)) _max = _stop - _start; \
+ _s1 += _stop - _start; \
+ _s2 += (_stop - _start) * (_stop - _start); \
+ } \
+ pa_log_debug("%s: %llu usec (avg: %g, min = %llu, max = %llu, stddev = %g).", _label, \
+ (long long unsigned int)_s1, \
+ ((double)_s1 / _times2), \
+ (long long unsigned int)_min, \
+ (long long unsigned int)_max, \
+ sqrt(_times2 * _s2 - _s1 * _s1) / _times2); \
+}
+
+#endif
diff --git a/src/tests/sig2str-test.c b/src/tests/sig2str-test.c
new file mode 100644
index 0000000..f1d82d8
--- /dev/null
+++ b/src/tests/sig2str-test.c
@@ -0,0 +1,127 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <check.h>
+
+#include <signal.h>
+#include <stdio.h>
+
+#include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
+
+static const char *names[] = {
+ "SIG-1",
+ "SIG0",
+ "SIGHUP",
+ "SIGINT",
+ "SIGQUIT",
+ "SIGULL",
+ "SIGTRAP",
+ "SIGABRT",
+ "SIGBUS",
+ "SIGFPE",
+ "SIGKILL",
+ "SIGUSR1",
+ "SIGSEGV",
+ "SIGUSR2",
+ "SIGPIPE",
+ "SIGALRM",
+ "SIGTERM",
+ "SIGSTKFLT",
+ "SIGCHLD",
+ "SIGCONT",
+ "SIGSTOP",
+ "SIGTSTP",
+ "SIGTTIN",
+ "SIGTTOU",
+ "SIGURG",
+ "SIGXCPU",
+ "SIGXFSZ",
+ "SIGVTALRM",
+ "SIGPROF",
+ "SIGWINCH",
+ "SIGIO",
+ "SIGPWR",
+ "SIGSYS",
+ "SIG32",
+ "SIG33",
+ "SIGRTMIN+0",
+ "SIGRTMIN+1",
+ "SIGRTMIN+2",
+ "SIGRTMIN+3",
+ "SIGRTMIN+4",
+ "SIGRTMIN+5",
+ "SIGRTMIN+6",
+ "SIGRTMIN+7",
+ "SIGRTMIN+8",
+ "SIGRTMIN+9",
+ "SIGRTMIN+10",
+ "SIGRTMIN+11",
+ "SIGRTMIN+12",
+ "SIGRTMIN+13",
+ "SIGRTMIN+14",
+ "SIGRTMIN+15",
+ "SIGRTMIN+16",
+ "SIGRTMIN+17",
+ "SIGRTMIN+18",
+ "SIGRTMIN+19",
+ "SIGRTMIN+20",
+ "SIGRTMIN+21",
+ "SIGRTMIN+22",
+ "SIGRTMIN+23",
+ "SIGRTMIN+24",
+ "SIGRTMIN+25",
+ "SIGRTMIN+26",
+ "SIGRTMIN+27",
+ "SIGRTMIN+28",
+ "SIGRTMIN+29",
+ "SIGRTMIN+30",
+ "SIG65"
+};
+
+START_TEST (sig2str_test) {
+ int sig;
+
+ for (sig = -1; sig <= NSIG; sig++) {
+ printf("%i = %s\n", sig, pa_sig2str(sig));
+ fail_unless(pa_streq(pa_sig2str(sig), names[sig+1]));
+ }
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ s = suite_create("Signal String");
+ tc = tcase_create("sig2str");
+ tcase_add_test(tc, sig2str_test);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/sigbus-test.c b/src/tests/sigbus-test.c
new file mode 100644
index 0000000..dbfc3d1
--- /dev/null
+++ b/src/tests/sigbus-test.c
@@ -0,0 +1,92 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Lennart Poettering
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <fcntl.h>
+#include <sys/mman.h>
+
+#include <check.h>
+
+#include <pulsecore/memtrap.h>
+#include <pulsecore/core-util.h>
+
+START_TEST (sigbus_test) {
+ void *p;
+ int fd;
+ pa_memtrap *m;
+ const size_t page_size = pa_page_size();
+
+ pa_log_set_level(PA_LOG_DEBUG);
+ pa_memtrap_install();
+
+ /* Create the memory map */
+ fail_unless((fd = open("sigbus-test-map", O_RDWR|O_TRUNC|O_CREAT, 0660)) >= 0);
+ fail_unless(unlink("sigbus-test-map") == 0);
+ fail_unless(ftruncate(fd, page_size) >= 0);
+ fail_unless((p = mmap(NULL, page_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0)) != MAP_FAILED);
+
+ /* Register memory map */
+ m = pa_memtrap_add(p, page_size);
+
+ /* Use memory map */
+ pa_snprintf(p, page_size, "This is a test that should work fine.");
+
+ /* Verify memory map */
+ pa_log("Let's see if this worked: %s", (char*) p);
+ pa_log("And memtrap says it is good: %s", pa_yes_no(pa_memtrap_is_good(m)));
+ fail_unless(pa_memtrap_is_good(m) == true);
+
+ /* Invalidate mapping */
+ fail_unless(ftruncate(fd, 0) >= 0);
+
+ /* Use memory map */
+ pa_snprintf(p, page_size, "This is a test that should fail but get caught.");
+
+ /* Verify memory map */
+ pa_log("Let's see if this worked: %s", (char*) p);
+ pa_log("And memtrap says it is good: %s", pa_yes_no(pa_memtrap_is_good(m)));
+ fail_unless(pa_memtrap_is_good(m) == false);
+
+ pa_memtrap_remove(m);
+ munmap(p, page_size);
+ close(fd);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ s = suite_create("Sig Bus");
+ tc = tcase_create("sigbus");
+ tcase_add_test(tc, sigbus_test);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/smoother-test.c b/src/tests/smoother-test.c
new file mode 100644
index 0000000..5e12f11
--- /dev/null
+++ b/src/tests/smoother-test.c
@@ -0,0 +1,104 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <check.h>
+
+#include <pulse/timeval.h>
+
+#include <pulsecore/log.h>
+#include <pulsecore/time-smoother.h>
+
+START_TEST (smoother_test) {
+ pa_usec_t x;
+ unsigned u = 0;
+ pa_smoother *s;
+ int m;
+
+/* unsigned msec[] = { */
+/* 200, 200, */
+/* 300, 320, */
+/* 400, 400, */
+/* 500, 480, */
+/* 0, 0 */
+/* }; */
+
+ int msec[200];
+
+ srand(0);
+
+ if (!getenv("MAKE_CHECK"))
+ pa_log_set_level(PA_LOG_DEBUG);
+
+ for (m = 0, u = 0; u < PA_ELEMENTSOF(msec); u+= 2) {
+
+ msec[u] = m+1 + (rand() % 100) - 50;
+ msec[u+1] = m + (rand() % 2000) - 1000 + 5000;
+
+ m += rand() % 100;
+
+ if (msec[u] < 0)
+ msec[u] = 0;
+
+ if (msec[u+1] < 0)
+ msec[u+1] = 0;
+ }
+
+ s = pa_smoother_new(700*PA_USEC_PER_MSEC, 2000*PA_USEC_PER_MSEC, false, true, 6, 0, true);
+
+ for (x = 0, u = 0; x < PA_USEC_PER_SEC * 10; x += PA_USEC_PER_MSEC) {
+
+ while (u < PA_ELEMENTSOF(msec) && (pa_usec_t) msec[u]*PA_USEC_PER_MSEC < x) {
+ pa_smoother_put(s, (pa_usec_t) msec[u] * PA_USEC_PER_MSEC, (pa_usec_t) msec[u+1] * PA_USEC_PER_MSEC);
+ pa_log_debug("%i\t\t%i", msec[u], msec[u+1]);
+ u += 2;
+
+ if (u < PA_ELEMENTSOF(msec))
+ pa_smoother_resume(s, (pa_usec_t) msec[u] * PA_USEC_PER_MSEC, true);
+ }
+
+ pa_log_debug("%llu\t%llu", (unsigned long long) (x/PA_USEC_PER_MSEC), (unsigned long long) (pa_smoother_get(s, x)/PA_USEC_PER_MSEC));
+ }
+
+ pa_smoother_free(s);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ s = suite_create("Smoother");
+ tc = tcase_create("smoother");
+ tcase_add_test(tc, smoother_test);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/srbchannel-test.c b/src/tests/srbchannel-test.c
new file mode 100644
index 0000000..0e7b0ce
--- /dev/null
+++ b/src/tests/srbchannel-test.c
@@ -0,0 +1,145 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2014 David Henningsson, Canonical Ltd.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <check.h>
+
+#include <pulse/mainloop.h>
+#include <pulsecore/packet.h>
+#include <pulsecore/pstream.h>
+#include <pulsecore/iochannel.h>
+#include <pulsecore/memblock.h>
+
+static unsigned packets_received;
+static unsigned packets_checksum;
+static size_t packets_length;
+
+static void packet_received(pa_pstream *p, pa_packet *packet, pa_cmsg_ancil_data *ancil_data, void *userdata) {
+ const uint8_t *pdata;
+ size_t plen;
+ unsigned i;
+
+ pdata = pa_packet_data(packet, &plen);
+ fail_unless(packets_length == plen);
+
+ packets_received++;
+ for (i = 0; i < plen; i++)
+ packets_checksum += pdata[i];
+}
+
+static void packet_test(unsigned npackets, size_t plength, pa_mainloop *ml, pa_pstream *p1, pa_pstream *p2) {
+ pa_packet *packet = pa_packet_new(plength);
+ unsigned i;
+ unsigned psum = 0, totalsum = 0;
+ uint8_t *pdata;
+ size_t plen;
+
+ pa_log_info("Sending %d packets of length %zd", npackets, plength);
+ packets_received = 0;
+ packets_checksum = 0;
+ packets_length = plength;
+ pa_pstream_set_receive_packet_callback(p2, packet_received, NULL);
+
+ pdata = (uint8_t *) pa_packet_data(packet, &plen);
+ for (i = 0; i < plen; i++) {
+ pdata[i] = i;
+ psum += pdata[i];
+ }
+
+ for (i = 0; i < npackets; i++) {
+ pa_pstream_send_packet(p1, packet, NULL);
+ totalsum += psum;
+ pa_mainloop_iterate(ml, 0, NULL);
+ }
+
+ while (packets_received < npackets)
+ pa_mainloop_iterate(ml, 1, NULL);
+
+ fail_unless(packets_checksum == totalsum);
+ pa_log_debug("Correct checksum received (%d)", packets_checksum);
+ pa_packet_unref(packet);
+}
+
+START_TEST (srbchannel_test) {
+
+ int pipefd[4];
+
+ pa_mainloop *ml = pa_mainloop_new();
+ pa_mempool *mp = pa_mempool_new(PA_MEM_TYPE_SHARED_POSIX, 0, true);
+ pa_iochannel *io1, *io2;
+ pa_pstream *p1, *p2;
+ pa_srbchannel *sr1, *sr2;
+ pa_srbchannel_template srt;
+
+ fail_unless(pipe(pipefd) == 0);
+ fail_unless(pipe(&pipefd[2]) == 0);
+ io1 = pa_iochannel_new(pa_mainloop_get_api(ml), pipefd[2], pipefd[1]);
+ io2 = pa_iochannel_new(pa_mainloop_get_api(ml), pipefd[0], pipefd[3]);
+ p1 = pa_pstream_new(pa_mainloop_get_api(ml), io1, mp);
+ p2 = pa_pstream_new(pa_mainloop_get_api(ml), io2, mp);
+
+ pa_log_debug("Pipes: fd %d -> %d, %d -> %d", pipefd[1], pipefd[0], pipefd[3], pipefd[2]);
+
+ packet_test(250, 5, ml, p1, p2);
+ packet_test(10, 1234567, ml, p1, p2);
+
+ pa_log_debug("And now the same thing with srbchannel...");
+
+ sr1 = pa_srbchannel_new(pa_mainloop_get_api(ml), mp);
+ pa_srbchannel_export(sr1, &srt);
+ pa_pstream_set_srbchannel(p1, sr1);
+ sr2 = pa_srbchannel_new_from_template(pa_mainloop_get_api(ml), &srt);
+ pa_pstream_set_srbchannel(p2, sr2);
+
+ packet_test(250, 5, ml, p1, p2);
+ packet_test(10, 1234567, ml, p1, p2);
+
+ pa_pstream_unref(p1);
+ pa_pstream_unref(p2);
+ pa_mempool_unref(mp);
+ pa_mainloop_free(ml);
+}
+END_TEST
+
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ if (!getenv("MAKE_CHECK"))
+ pa_log_set_level(PA_LOG_DEBUG);
+
+ s = suite_create("srbchannel");
+ tc = tcase_create("srbchannel");
+ tcase_add_test(tc, srbchannel_test);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/stripnul.c b/src/tests/stripnul.c
new file mode 100644
index 0000000..e4b07aa
--- /dev/null
+++ b/src/tests/stripnul.c
@@ -0,0 +1,71 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/macro.h>
+
+#define MAX_BUFFER (16*1024)
+
+int main(int argc, char *argv[]) {
+ FILE *i, *o;
+ size_t granularity;
+ bool found = false;
+ uint8_t *zero;
+
+ pa_assert_se(argc >= 2);
+ pa_assert_se((granularity = (size_t) atoi(argv[1])) >= 1);
+ pa_assert(granularity <= MAX_BUFFER);
+ pa_assert_se((i = (argc >= 3) ? fopen(argv[2], "r") : stdin));
+ pa_assert_se((o = (argc >= 4) ? fopen(argv[3], "w") : stdout));
+
+ zero = pa_xmalloc0(granularity);
+
+ for (;;) {
+ uint8_t buffer[MAX_BUFFER], *p;
+ size_t k;
+
+ k = fread(buffer, granularity, sizeof(buffer)/granularity, i);
+
+ if (k <= 0)
+ break;
+
+ if (found)
+ pa_assert_se(fwrite(buffer, granularity, k, o) == k);
+ else {
+ for (p = buffer; ((size_t) (p-buffer)/granularity) < k; p += granularity)
+ if (memcmp(p, zero, granularity)) {
+ size_t left;
+ found = true;
+ left = (size_t) (k - (size_t) (p-buffer)/granularity);
+ pa_assert_se(fwrite(p, granularity, left, o) == left);
+ break;
+ }
+ }
+ }
+
+ fflush(o);
+
+ return 0;
+}
diff --git a/src/tests/strlist-test.c b/src/tests/strlist-test.c
new file mode 100644
index 0000000..f4ec1c3
--- /dev/null
+++ b/src/tests/strlist-test.c
@@ -0,0 +1,70 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <check.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/strlist.h>
+#include <pulsecore/core-util.h>
+
+START_TEST (strlist_test) {
+ char *t, *u;
+ pa_strlist *l = NULL;
+
+ l = pa_strlist_prepend(l, "e");
+ l = pa_strlist_prepend(l, "d");
+ l = pa_strlist_prepend(l, "c");
+ l = pa_strlist_prepend(l, "b");
+ l = pa_strlist_prepend(l, "a");
+
+ t = pa_strlist_to_string(l);
+ pa_strlist_free(l);
+
+ fprintf(stderr, "1: %s\n", t);
+ fail_unless(pa_streq(t, "a b c d e"));
+
+ l = pa_strlist_parse(t);
+ pa_xfree(t);
+
+ t = pa_strlist_to_string(l);
+ fprintf(stderr, "2: %s\n", t);
+ fail_unless(pa_streq(t, "a b c d e"));
+ pa_xfree(t);
+
+ l = pa_strlist_pop(l, &u);
+ fprintf(stderr, "3: %s\n", u);
+ fail_unless(pa_streq(u, "a"));
+ pa_xfree(u);
+
+ l = pa_strlist_remove(l, "c");
+
+ t = pa_strlist_to_string(l);
+ fprintf(stderr, "4: %s\n", t);
+ fail_unless(pa_streq(t, "b d e"));
+ pa_xfree(t);
+
+ pa_strlist_free(l);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ s = suite_create("StrList");
+ tc = tcase_create("strlist");
+ tcase_add_test(tc, strlist_test);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/sync-playback.c b/src/tests/sync-playback.c
new file mode 100644
index 0000000..9ef038c
--- /dev/null
+++ b/src/tests/sync-playback.c
@@ -0,0 +1,219 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <signal.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include <check.h>
+
+#include <pulse/pulseaudio.h>
+#include <pulse/mainloop.h>
+
+#define NSTREAMS 4
+#define SINE_HZ 440
+#define SAMPLE_HZ 8000
+
+static pa_context *context = NULL;
+static pa_stream *streams[NSTREAMS];
+static pa_mainloop_api *mainloop_api = NULL;
+static const char *bname = NULL;
+
+static float data[SAMPLE_HZ]; /* one second space */
+
+static int n_streams_ready = 0;
+
+static const pa_sample_spec sample_spec = {
+ .format = PA_SAMPLE_FLOAT32,
+ .rate = SAMPLE_HZ,
+ .channels = 1
+};
+
+static const pa_buffer_attr buffer_attr = {
+ .maxlength = SAMPLE_HZ*sizeof(float)*NSTREAMS, /* exactly space for the entire play time */
+ .tlength = (uint32_t) -1,
+ .prebuf = 0, /* Setting prebuf to 0 guarantees us the streams will run synchronously, no matter what */
+ .minreq = (uint32_t) -1,
+ .fragsize = 0
+};
+
+static void nop_free_cb(void *p) {}
+
+static void underflow_cb(struct pa_stream *s, void *userdata) {
+ int i = (int) (long) userdata;
+
+ fprintf(stderr, "Stream %i finished\n", i);
+
+ if (++n_streams_ready >= 2*NSTREAMS) {
+ fprintf(stderr, "We're done\n");
+ mainloop_api->quit(mainloop_api, 0);
+ }
+}
+
+/* This routine is called whenever the stream state changes */
+static void stream_state_callback(pa_stream *s, void *userdata) {
+ fail_unless(s != NULL);
+
+ switch (pa_stream_get_state(s)) {
+ case PA_STREAM_UNCONNECTED:
+ case PA_STREAM_CREATING:
+ case PA_STREAM_TERMINATED:
+ break;
+
+ case PA_STREAM_READY: {
+
+ int r, i = (int) (long) userdata;
+
+ fprintf(stderr, "Writing data to stream %i.\n", i);
+
+ r = pa_stream_write(s, data, sizeof(data), nop_free_cb, (int64_t) sizeof(data) * (int64_t) i, PA_SEEK_ABSOLUTE);
+ fail_unless(r == 0);
+
+ /* Be notified when this stream is drained */
+ pa_stream_set_underflow_callback(s, underflow_cb, userdata);
+
+ /* All streams have been set up, let's go! */
+ if (++n_streams_ready >= NSTREAMS) {
+ fprintf(stderr, "Uncorking\n");
+ pa_operation_unref(pa_stream_cork(s, 0, NULL, NULL));
+ }
+
+ break;
+ }
+
+ default:
+ case PA_STREAM_FAILED:
+ fprintf(stderr, "Stream error: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
+ ck_abort();
+ }
+}
+
+/* This is called whenever the context status changes */
+static void context_state_callback(pa_context *c, void *userdata) {
+ fail_unless(c != NULL);
+
+ switch (pa_context_get_state(c)) {
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+
+ case PA_CONTEXT_READY: {
+
+ int i;
+ fprintf(stderr, "Connection established.\n");
+
+ for (i = 0; i < NSTREAMS; i++) {
+ char name[64];
+
+ fprintf(stderr, "Creating stream %i\n", i);
+
+ snprintf(name, sizeof(name), "stream #%i", i);
+
+ streams[i] = pa_stream_new(c, name, &sample_spec, NULL);
+ fail_unless(streams[i] != NULL);
+ pa_stream_set_state_callback(streams[i], stream_state_callback, (void*) (long) i);
+ pa_stream_connect_playback(streams[i], NULL, &buffer_attr, PA_STREAM_START_CORKED, NULL, i == 0 ? NULL : streams[0]);
+ }
+
+ break;
+ }
+
+ case PA_CONTEXT_TERMINATED:
+ mainloop_api->quit(mainloop_api, 0);
+ break;
+
+ case PA_CONTEXT_FAILED:
+ default:
+ fprintf(stderr, "Context error: %s\n", pa_strerror(pa_context_errno(c)));
+ ck_abort();
+ }
+}
+
+START_TEST (sync_playback_test) {
+ pa_mainloop* m = NULL;
+ int i, ret = 0;
+
+ for (i = 0; i < SAMPLE_HZ; i++)
+ data[i] = (float) sin(((double) i/SAMPLE_HZ)*2*M_PI*SINE_HZ)/2;
+
+ for (i = 0; i < NSTREAMS; i++)
+ streams[i] = NULL;
+
+ /* Set up a new main loop */
+ m = pa_mainloop_new();
+ fail_unless(m != NULL);
+
+ mainloop_api = pa_mainloop_get_api(m);
+
+ context = pa_context_new(mainloop_api, bname);
+ fail_unless(context != NULL);
+
+ pa_context_set_state_callback(context, context_state_callback, NULL);
+
+ /* Connect the context */
+ if (pa_context_connect(context, NULL, 0, NULL) < 0) {
+ fprintf(stderr, "pa_context_connect() failed.\n");
+ goto quit;
+ }
+
+ if (pa_mainloop_run(m, &ret) < 0)
+ fprintf(stderr, "pa_mainloop_run() failed.\n");
+
+quit:
+ pa_context_unref(context);
+
+ for (i = 0; i < NSTREAMS; i++)
+ if (streams[i])
+ pa_stream_unref(streams[i]);
+
+ pa_mainloop_free(m);
+
+ fail_unless(ret == 0);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ bname = argv[0];
+
+ s = suite_create("Sync Playback");
+ tc = tcase_create("syncplayback");
+ tcase_add_test(tc, sync_playback_test);
+ /* 4s of audio, 0.5s grace time */
+ tcase_set_timeout(tc, 4.5);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/test-daemon.meson.sh b/src/tests/test-daemon.meson.sh
new file mode 100644
index 0000000..11e29eb
--- /dev/null
+++ b/src/tests/test-daemon.meson.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+set -e
+set -u
+
+PATH=${MESON_BUILD_ROOT}/src/daemon:${MESON_BUILD_ROOT}/src/tests:${MESON_BUILD_ROOT}/src/utils:${PATH}
+export PATH
+
+${MESON_SOURCE_ROOT}/src/tests/test-daemon.sh $@
diff --git a/src/tests/thread-mainloop-test.c b/src/tests/thread-mainloop-test.c
new file mode 100644
index 0000000..5f6952c
--- /dev/null
+++ b/src/tests/thread-mainloop-test.c
@@ -0,0 +1,117 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+
+#include <check.h>
+
+#include <pulse/rtclock.h>
+#include <pulse/timeval.h>
+#include <pulse/util.h>
+#include <pulse/thread-mainloop.h>
+
+#include <pulsecore/core-rtclock.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/mutex.h>
+
+static void tcb(pa_mainloop_api *a, pa_time_event *e, const struct timeval *tv, void *userdata) {
+ pa_assert_se(pa_threaded_mainloop_in_thread(userdata));
+ fprintf(stderr, "TIME EVENT START\n");
+ pa_threaded_mainloop_signal(userdata, 1);
+ fprintf(stderr, "TIME EVENT END\n");
+}
+
+static void ocb(pa_threaded_mainloop *m, void *userdata) {
+ pa_threaded_mainloop_lock(m);
+ pa_threaded_mainloop_signal(m, 0);
+ pa_threaded_mainloop_unlock(m);
+}
+
+START_TEST (thread_mainloop_test) {
+ pa_mainloop_api *a;
+ pa_threaded_mainloop *m;
+ struct timeval tv;
+
+ m = pa_threaded_mainloop_new();
+ fail_unless(m != NULL);
+ a = pa_threaded_mainloop_get_api(m);
+ fail_unless(m != NULL);
+
+ fail_unless(pa_threaded_mainloop_start(m) >= 0);
+
+ pa_threaded_mainloop_lock(m);
+
+ fail_unless(!pa_threaded_mainloop_in_thread(m));
+
+ a->time_new(a, pa_timeval_rtstore(&tv, pa_rtclock_now() + 5 * PA_USEC_PER_SEC, true), tcb, m);
+
+ fprintf(stderr, "waiting 5s (signal)\n");
+ pa_threaded_mainloop_wait(m);
+ fprintf(stderr, "wait completed\n");
+ pa_threaded_mainloop_accept(m);
+ fprintf(stderr, "signal accepted\n");
+
+ pa_threaded_mainloop_unlock(m);
+
+ fprintf(stderr, "waiting 5s (sleep)\n");
+ pa_msleep(5000);
+
+ /* Test pa_threaded_mainloop_once_unlocked() */
+ pa_threaded_mainloop_lock(m);
+
+ fprintf(stderr, "scheduling unlocked callback\n");
+ pa_threaded_mainloop_once_unlocked(m, ocb, NULL);
+
+ pa_threaded_mainloop_wait(m);
+ fprintf(stderr, "got unlocked callback\n");
+
+ pa_threaded_mainloop_unlock(m);
+
+ pa_threaded_mainloop_stop(m);
+
+ pa_threaded_mainloop_free(m);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ s = suite_create("Thread MainLoop");
+ tc = tcase_create("threadmainloop");
+ tcase_add_test(tc, thread_mainloop_test);
+ /* the default timeout is too small,
+ * set it to a reasonable large one.
+ */
+ tcase_set_timeout(tc, 60 * 60);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/thread-test.c b/src/tests/thread-test.c
new file mode 100644
index 0000000..0c83e67
--- /dev/null
+++ b/src/tests/thread-test.c
@@ -0,0 +1,164 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <check.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/mutex.h>
+#include <pulsecore/once.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-util.h>
+
+static pa_mutex *mutex = NULL;
+static pa_cond *cond1 = NULL, *cond2 = NULL;
+static pa_tls *tls = NULL;
+
+static int magic_number = 0;
+
+#define THREADS_MAX 20
+
+static void once_func(void) {
+ pa_log("once!");
+}
+
+static pa_once once = PA_ONCE_INIT;
+
+static void thread_func(void *data) {
+ pa_tls_set(tls, data);
+
+ pa_log_info("thread_func() for %s starting...", (char*) pa_tls_get(tls));
+
+ pa_mutex_lock(mutex);
+
+ for (;;) {
+ int k, n;
+
+ pa_log_info("%s waiting ...", (char*) pa_tls_get(tls));
+
+ for (;;) {
+
+ if (magic_number < 0)
+ goto quit;
+
+ if (magic_number != 0)
+ break;
+
+ pa_cond_wait(cond1, mutex);
+ }
+
+ k = magic_number;
+ magic_number = 0;
+
+ pa_mutex_unlock(mutex);
+
+ pa_run_once(&once, once_func);
+
+ pa_cond_signal(cond2, 0);
+
+ pa_log_info("%s got number %i", (char*) pa_tls_get(tls), k);
+
+ /* Spin! */
+ for (n = 0; n < k; n++)
+ pa_thread_yield();
+
+ pa_mutex_lock(mutex);
+ }
+
+quit:
+
+ pa_mutex_unlock(mutex);
+
+ pa_log_info("thread_func() for %s done...", (char*) pa_tls_get(tls));
+}
+
+START_TEST (thread_test) {
+ int i, k;
+ pa_thread* t[THREADS_MAX];
+
+ if (!getenv("MAKE_CHECK"))
+ pa_log_set_level(PA_LOG_DEBUG);
+
+ mutex = pa_mutex_new(false, false);
+ cond1 = pa_cond_new();
+ cond2 = pa_cond_new();
+ tls = pa_tls_new(pa_xfree);
+
+ for (i = 0; i < THREADS_MAX; i++) {
+ t[i] = pa_thread_new("test", thread_func, pa_sprintf_malloc("Thread #%i", i+1));
+ fail_unless(t[i] != 0);
+ }
+
+ pa_mutex_lock(mutex);
+
+ pa_log("loop-init");
+
+ for (k = 0; k < 100; k++) {
+ pa_assert(magic_number == 0);
+
+ /* There's a thread waiting for us to change magic_number to a non-zero
+ * value. The "+ 1" part ensures that we don't accidentally set
+ * magic_number to zero here. */
+ magic_number = (int) rand() % 0x10000 + 1;
+
+ pa_log_info("iteration %i (%i)", k, magic_number);
+
+ pa_cond_signal(cond1, 0);
+
+ pa_cond_wait(cond2, mutex);
+ }
+
+ pa_log("loop-exit");
+
+ magic_number = -1;
+ pa_cond_signal(cond1, 1);
+
+ pa_mutex_unlock(mutex);
+
+ for (i = 0; i < THREADS_MAX; i++)
+ pa_thread_free(t[i]);
+
+ pa_mutex_free(mutex);
+ pa_cond_free(cond1);
+ pa_cond_free(cond2);
+ pa_tls_free(tls);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ s = suite_create("Thread");
+ tc = tcase_create("thread");
+ tcase_add_test(tc, thread_test);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/usergroup-test.c b/src/tests/usergroup-test.c
new file mode 100644
index 0000000..3525e5a
--- /dev/null
+++ b/src/tests/usergroup-test.c
@@ -0,0 +1,167 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Ted Percival
+
+ PulseAudio 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.
+
+ PulseAudio 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 PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+#include <errno.h>
+
+#include <check.h>
+
+#include <pulsecore/usergroup.h>
+#include <pulsecore/core-util.h>
+
+static int load_reference_structs(struct group **gr, struct passwd **pw) {
+ setpwent();
+ *pw = getpwent();
+ endpwent();
+
+ setgrent();
+ *gr = getgrent();
+ endgrent();
+
+ return (*gr && *pw) ? 0 : 1;
+}
+
+static int compare_group(const struct group *a, const struct group *b) {
+ char **amem, **bmem;
+
+ if (!pa_streq(a->gr_name, b->gr_name)) {
+ fprintf(stderr, "Group name mismatch: [%s] [%s]\n", a->gr_name, b->gr_name);
+ return 1;
+ }
+
+ if (!pa_streq(a->gr_passwd, b->gr_passwd)) {
+ fprintf(stderr, "Group password mismatch: [%s] [%s]\n", a->gr_passwd, b->gr_passwd);
+ return 1;
+ }
+
+ if (a->gr_gid != b->gr_gid) {
+ fprintf(stderr, "Gid mismatch: [%lu] [%lu]\n", (unsigned long) a->gr_gid, (unsigned long) b->gr_gid);
+ return 1;
+ }
+
+ /* XXX: Assuming the group ordering is identical. */
+ for (amem = a->gr_mem, bmem = b->gr_mem; *amem && *bmem; ++amem, ++bmem) {
+ if (!pa_streq(*amem, *bmem)) {
+ fprintf(stderr, "Group member mismatch: [%s] [%s]\n", *amem, *bmem);
+ return 1;
+ }
+ }
+
+ if (*amem || *bmem) {
+ fprintf(stderr, "Mismatched group count\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+static int compare_passwd(const struct passwd *a, const struct passwd *b) {
+ if (!pa_streq(a->pw_name, b->pw_name)) {
+ fprintf(stderr, "pw_name mismatch: [%s] [%s]\n", a->pw_name, b->pw_name);
+ return 1;
+ }
+
+ if (!pa_streq(a->pw_passwd, b->pw_passwd)) {
+ fprintf(stderr, "pw_passwd mismatch: [%s] [%s]\n", a->pw_passwd, b->pw_passwd);
+ return 1;
+ }
+
+ if (a->pw_uid != b->pw_uid) {
+ fprintf(stderr, "pw_uid mismatch: [%lu] [%lu]\n", (unsigned long) a->pw_uid, (unsigned long) b->pw_uid);
+ return 1;
+ }
+
+ if (a->pw_gid != b->pw_gid) {
+ fprintf(stderr, "pw_gid mismatch: [%lu] [%lu]\n", (unsigned long) a->pw_gid, (unsigned long) b->pw_gid);
+ return 1;
+ }
+
+ if (!pa_streq(a->pw_gecos, b->pw_gecos)) {
+ fprintf(stderr, "pw_gecos mismatch: [%s] [%s]\n", a->pw_gecos, b->pw_gecos);
+ return 1;
+ }
+
+ if (!pa_streq(a->pw_dir, b->pw_dir)) {
+ fprintf(stderr, "pw_dir mismatch: [%s] [%s]\n", a->pw_dir, b->pw_dir);
+ return 1;
+ }
+
+ if (!pa_streq(a->pw_shell, b->pw_shell)) {
+ fprintf(stderr, "pw_shell mismatch: [%s] [%s]\n", a->pw_shell, b->pw_shell);
+ return 1;
+ }
+
+ return 0;
+}
+
+START_TEST (usergroup_test) {
+ struct group *gr;
+ struct passwd *pw;
+ struct group *reference_group = NULL;
+ struct passwd *reference_passwd = NULL;
+
+ fail_if(load_reference_structs(&reference_group, &reference_passwd));
+
+ errno = 0;
+ gr = pa_getgrgid_malloc(reference_group->gr_gid);
+ fail_if(compare_group(reference_group, gr));
+ pa_getgrgid_free(gr);
+
+ errno = 0;
+ gr = pa_getgrnam_malloc(reference_group->gr_name);
+ fail_if(compare_group(reference_group, gr));
+ pa_getgrnam_free(gr);
+
+ errno = 0;
+ pw = pa_getpwuid_malloc(reference_passwd->pw_uid);
+ fail_if(compare_passwd(reference_passwd, pw));
+ pa_getpwuid_free(pw);
+
+ errno = 0;
+ pw = pa_getpwnam_malloc(reference_passwd->pw_name);
+ fail_if(compare_passwd(reference_passwd, pw));
+ pa_getpwnam_free(pw);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ s = suite_create("Usergroup");
+ tc = tcase_create("usergroup");
+ tcase_add_test(tc, usergroup_test);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/utf8-test.c b/src/tests/utf8-test.c
new file mode 100644
index 0000000..1e1e777
--- /dev/null
+++ b/src/tests/utf8-test.c
@@ -0,0 +1,72 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <check.h>
+
+#include <pulse/utf8.h>
+#include <pulse/xmalloc.h>
+#include <pulsecore/core-util.h>
+
+START_TEST (utf8_valid) {
+ fail_unless(pa_utf8_valid("hallo") != NULL);
+ fail_unless(pa_utf8_valid("hallo\n") != NULL);
+ fail_unless(pa_utf8_valid("hüpfburg\n") == NULL);
+ fail_unless(pa_utf8_valid("hallo\n") != NULL);
+ fail_unless(pa_utf8_valid("hüpfburg\n") != NULL);
+}
+END_TEST
+
+START_TEST (utf8_filter) {
+ char *c;
+
+ {
+ char res1[] = { 0x68, 0x5f, 0x70, 0x66, 0x62, 0x75, 0x72, 0x67, '\0' };
+ c = pa_utf8_filter("hüpfburg");
+ pa_log_debug("%s %s", res1, c);
+ fail_unless(pa_streq(c, res1));
+ pa_xfree(c);
+ }
+
+ {
+ char res2[] = { 0x68, 0xc3, 0xbc, 0x70, 0x66, 0x62, 0x75, 0x72, 0x67, '\0' };
+ c = pa_utf8_filter("hüpfburg");
+ fail_unless(pa_streq(c, res2));
+ pa_log_debug("%s %s", res2, c);
+ pa_xfree(c);
+ }
+
+ {
+ char res3[] = { 0x5f, 0x78, 0x6b, 0x6e, 0x5f, 0x72, 0x7a, 0x6d, 0x5f, 0x72, 0x7a, 0x65, 0x6c, 0x74, 0x5f, 0x72, 0x73, 0x7a, 0xdf, 0xb3, 0x5f, 0x64, 0x73, 0x6a, 0x6b, 0x66, 0x68, '\0' };
+ c = pa_utf8_filter("üxknärzmörzeltörszß³§dsjkfh");
+ pa_log_debug("%s %s", res3, c);
+ fail_unless(pa_streq(c, res3));
+ pa_xfree(c);
+ }
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ if (!getenv("MAKE_CHECK"))
+ pa_log_set_level(PA_LOG_DEBUG);
+
+ s = suite_create("UTF8");
+ tc = tcase_create("utf8");
+ tcase_add_test(tc, utf8_valid);
+ tcase_add_test(tc, utf8_filter);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/tests/volume-test.c b/src/tests/volume-test.c
new file mode 100644
index 0000000..55486f6
--- /dev/null
+++ b/src/tests/volume-test.c
@@ -0,0 +1,173 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio 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.
+
+ PulseAudio 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+
+#include <check.h>
+
+#include <pulse/volume.h>
+
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+START_TEST (volume_test) {
+ pa_volume_t v;
+ pa_cvolume cv;
+ float b;
+ pa_channel_map map;
+ pa_volume_t md = 0;
+ unsigned mdn = 0;
+
+ if (!getenv("MAKE_CHECK"))
+ pa_log_set_level(PA_LOG_DEBUG);
+
+ pa_log("Attenuation of sample 1 against 32767: %g dB", 20.0*log10(1.0/32767.0));
+ pa_log("Smallest possible attenuation > 0 applied to 32767: %li", lrint(32767.0*pa_sw_volume_to_linear(1)));
+
+ for (v = PA_VOLUME_MUTED; v <= PA_VOLUME_NORM*2; v += 256) {
+
+ double dB = pa_sw_volume_to_dB(v);
+ double f = pa_sw_volume_to_linear(v);
+
+ pa_log_debug("Volume: %3i; percent: %i%%; decibel %0.2f; linear = %0.2f; volume(decibel): %3i; volume(linear): %3i",
+ v, (v*100)/PA_VOLUME_NORM, dB, f, pa_sw_volume_from_dB(dB), pa_sw_volume_from_linear(f));
+ }
+
+ map.channels = cv.channels = 2;
+ map.map[0] = PA_CHANNEL_POSITION_LEFT;
+ map.map[1] = PA_CHANNEL_POSITION_RIGHT;
+
+ for (v = PA_VOLUME_MUTED; v <= PA_VOLUME_NORM*2; v += 256) {
+ char s[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
+
+ pa_cvolume_set(&cv, 2, v);
+
+ pa_log_debug("Volume: %3i [%s]", v, pa_cvolume_snprint_verbose(s, sizeof(s), &cv, &map, true));
+ }
+
+ for (cv.values[0] = PA_VOLUME_MUTED; cv.values[0] <= PA_VOLUME_NORM*2; cv.values[0] += 4096)
+ for (cv.values[1] = PA_VOLUME_MUTED; cv.values[1] <= PA_VOLUME_NORM*2; cv.values[1] += 4096) {
+ char s[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
+
+ pa_log_debug("Volume: [%s]; balance: %2.1f",
+ pa_cvolume_snprint_verbose(s, sizeof(s), &cv, &map, true),
+ pa_cvolume_get_balance(&cv, &map));
+ }
+
+ for (cv.values[0] = PA_VOLUME_MUTED+4096; cv.values[0] <= PA_VOLUME_NORM*2; cv.values[0] += 4096)
+ for (cv.values[1] = PA_VOLUME_MUTED; cv.values[1] <= PA_VOLUME_NORM*2; cv.values[1] += 4096)
+ for (b = -1.0f; b <= 1.0f; b += 0.2f) {
+ char s[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
+ pa_cvolume r;
+ float k;
+
+ pa_log_debug("Before: volume: [%s]; balance: %2.1f",
+ pa_cvolume_snprint_verbose(s, sizeof(s), &cv, &map, true),
+ pa_cvolume_get_balance(&cv, &map));
+
+ r = cv;
+ pa_cvolume_set_balance(&r, &map,b);
+
+ k = pa_cvolume_get_balance(&r, &map);
+ pa_log_debug("After: volume: [%s]; balance: %2.1f (intended: %2.1f) %s",
+ pa_cvolume_snprint_verbose(s, sizeof(s), &r, &map, true),
+ k,
+ b,
+ k < b - .05 || k > b + .5 ? "MISMATCH" : "");
+ }
+
+ for (v = PA_VOLUME_MUTED; v <= PA_VOLUME_NORM*2; v += 51) {
+
+ double l = pa_sw_volume_to_linear(v);
+ pa_volume_t k = pa_sw_volume_from_linear(l);
+ double db = pa_sw_volume_to_dB(v);
+ pa_volume_t r = pa_sw_volume_from_dB(db);
+ pa_volume_t w;
+
+ fail_unless(k == v);
+ fail_unless(r == v);
+
+ for (w = PA_VOLUME_MUTED; w < PA_VOLUME_NORM*2; w += 37) {
+
+ double t = pa_sw_volume_to_linear(w);
+ double db2 = pa_sw_volume_to_dB(w);
+ pa_volume_t p, p1, p2;
+ pa_volume_t md_local = 0;
+ double q, qq;
+
+ p = pa_sw_volume_multiply(v, w);
+ if (isfinite(db) && isfinite(db2))
+ qq = db + db2;
+ else
+ qq = -INFINITY;
+ p2 = pa_sw_volume_from_dB(qq);
+ q = l*t;
+ p1 = pa_sw_volume_from_linear(q);
+
+ if (p2 > p)
+ md_local = p2 - p;
+ else
+ md_local = p - p2;
+
+ if (p1 > p && p1 - p > md_local)
+ md_local = p1 - p;
+ if (p1 < p && p - p1 > md_local)
+ md_local = p - p1;
+
+ /* compute the number of times the deviation is over the acceptable threshold */
+ if (md_local > 1)
+ mdn++;
+
+ if (md_local > md)
+ md = md_local;
+ }
+ }
+
+ /*
+ * As the hardware, the compiler version and the compilation flags may
+ * generate rounding issues, we allow p1 and p2 to have a difference of + or - 1.
+ */
+ pa_log("max deviation: %lu, number of times over 1:%lu", (unsigned long) md, (unsigned long) mdn);
+
+ fail_unless(md <= 1);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ s = suite_create("Volume");
+ tc = tcase_create("volume");
+ tcase_add_test(tc, volume_test);
+ tcase_set_timeout(tc, 120);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}