diff options
Diffstat (limited to 'src/tests')
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, ×tamp); + 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(×tamp, &last_timestamp, sizeof(timestamp)) == 0 && + avail == last_avail && + delay == last_delay) { + /* This is boring */ + continue; + } + + now_us = timespec_us(&now); + timestamp_us = timespec_us(×tamp); + + 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; +} |