diff options
Diffstat (limited to 'media/libcubeb/tests')
-rw-r--r-- | media/libcubeb/tests/common.h | 61 | ||||
-rw-r--r-- | media/libcubeb/tests/moz.build | 80 | ||||
-rw-r--r-- | media/libcubeb/tests/test_audio.cpp | 294 | ||||
-rw-r--r-- | media/libcubeb/tests/test_devices.cpp | 162 | ||||
-rw-r--r-- | media/libcubeb/tests/test_duplex.cpp | 151 | ||||
-rw-r--r-- | media/libcubeb/tests/test_latency.cpp | 60 | ||||
-rw-r--r-- | media/libcubeb/tests/test_record.cpp | 125 | ||||
-rw-r--r-- | media/libcubeb/tests/test_resampler.cpp | 554 | ||||
-rw-r--r-- | media/libcubeb/tests/test_sanity.cpp | 676 | ||||
-rw-r--r-- | media/libcubeb/tests/test_tone.cpp | 149 | ||||
-rw-r--r-- | media/libcubeb/tests/test_utils.cpp | 80 |
11 files changed, 2392 insertions, 0 deletions
diff --git a/media/libcubeb/tests/common.h b/media/libcubeb/tests/common.h new file mode 100644 index 000000000..051f3c42a --- /dev/null +++ b/media/libcubeb/tests/common.h @@ -0,0 +1,61 @@ +/* + * Copyright © 2013 Sebastien Alaiwan + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ + +#if defined( _WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> +#else +#include <unistd.h> +#endif + +void delay(unsigned int ms) +{ +#if defined(_WIN32) + Sleep(ms); +#else + sleep(ms / 1000); + usleep(ms % 1000 * 1000); +#endif +} + +#if !defined(M_PI) +#define M_PI 3.14159265358979323846 +#endif + +int has_available_input_device(cubeb * ctx) +{ + cubeb_device_collection * devices; + int input_device_available = 0; + int r; + /* Bail out early if the host does not have input devices. */ + r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_INPUT, &devices); + if (r != CUBEB_OK) { + fprintf(stderr, "error enumerating devices."); + return 0; + } + + if (devices->count == 0) { + fprintf(stderr, "no input device available, skipping test.\n"); + return 0; + } + + for (uint32_t i = 0; i < devices->count; i++) { + input_device_available |= (devices->device[i]->state == + CUBEB_DEVICE_STATE_ENABLED); + } + + if (!input_device_available) { + fprintf(stderr, "there are input devices, but they are not " + "available, skipping\n"); + return 0; + } + + return 1; +} + diff --git a/media/libcubeb/tests/moz.build b/media/libcubeb/tests/moz.build new file mode 100644 index 000000000..1b17c7b1c --- /dev/null +++ b/media/libcubeb/tests/moz.build @@ -0,0 +1,80 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DEFINES['CUBEB_GECKO_BUILD'] = True + +GeckoCppUnitTests([ + 'test_duplex', + 'test_record', + 'test_tone', + 'test_utils' +]) + +if CONFIG['MOZ_PULSEAUDIO'] or CONFIG['OS_TARGET'] in ('Darwin', 'WINNT', 'Android'): + GeckoCppUnitTests([ + 'test_resampler', + ]) + +if CONFIG['OS_TARGET'] != 'Android': + GeckoCppUnitTests([ + 'test_audio', + 'test_latency', + 'test_sanity' + ]) + +LOCAL_INCLUDES += [ + '../include', + '../src' +] + +USE_LIBS += [ + 'cubeb', + 'speex' +] + +if CONFIG['OS_ARCH'] == 'WINNT': + # On windows, the WASAPI backend needs the resampler we have in + # /media/libspeex_resampler, so we can't get away with just linking cubeb's + # .o + USE_LIBS += [ + 'cubeb', + 'speex', + ] + OS_LIBS += [ + 'ole32' + ] +else: + # Otherwise, we can just grab all the compiled .o and compile against that, + # linking the appriopriate libraries. + USE_LIBS += [ + 'cubeb', + ] + # Don't link gkmedias for it introduces dependencies on Android. + if CONFIG['OS_TARGET'] == 'Android': + USE_LIBS += [ + 'speex', + ] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + OS_LIBS += [ + '-framework AudioUnit', + '-framework CoreAudio', + ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'uikit': + OS_LIBS += [ + '-framework CoreFoundation', + '-framework AudioToolbox', + ] +elif CONFIG['OS_TARGET'] == 'OpenBSD': + OS_LIBS += [ + 'sndio', + ] +else: + OS_LIBS += CONFIG['MOZ_ALSA_LIBS'] + OS_LIBS += CONFIG['MOZ_PULSEAUDIO_LIBS'] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/media/libcubeb/tests/test_audio.cpp b/media/libcubeb/tests/test_audio.cpp new file mode 100644 index 000000000..4943223e6 --- /dev/null +++ b/media/libcubeb/tests/test_audio.cpp @@ -0,0 +1,294 @@ +/* + * Copyright © 2013 Sebastien Alaiwan <sebastien.alaiwan@gmail.com> + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ + +/* libcubeb api/function exhaustive test. Plays a series of tones in different + * conditions. */ +#ifdef NDEBUG +#undef NDEBUG +#endif +#define _XOPEN_SOURCE 600 +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include <assert.h> +#include <string.h> + +#include "cubeb/cubeb.h" +#include "common.h" +#ifdef CUBEB_GECKO_BUILD +#include "TestHarness.h" +#endif + +#define MAX_NUM_CHANNELS 32 + +#if !defined(M_PI) +#define M_PI 3.14159265358979323846 +#endif + +#define NELEMS(x) ((int) (sizeof(x) / sizeof(x[0]))) +#define VOLUME 0.2 + +float get_frequency(int channel_index) +{ + return 220.0f * (channel_index+1); +} + +/* store the phase of the generated waveform */ +typedef struct { + int num_channels; + float phase[MAX_NUM_CHANNELS]; + float sample_rate; +} synth_state; + +synth_state* synth_create(int num_channels, float sample_rate) +{ + synth_state* synth = (synth_state *) malloc(sizeof(synth_state)); + if (!synth) + return NULL; + for(int i=0;i < MAX_NUM_CHANNELS;++i) + synth->phase[i] = 0.0f; + synth->num_channels = num_channels; + synth->sample_rate = sample_rate; + return synth; +} + +void synth_destroy(synth_state* synth) +{ + free(synth); +} + +void synth_run_float(synth_state* synth, float* audiobuffer, long nframes) +{ + for(int c=0;c < synth->num_channels;++c) { + float freq = get_frequency(c); + float phase_inc = 2.0 * M_PI * freq / synth->sample_rate; + for(long n=0;n < nframes;++n) { + audiobuffer[n*synth->num_channels+c] = sin(synth->phase[c]) * VOLUME; + synth->phase[c] += phase_inc; + } + } +} + +long data_cb_float(cubeb_stream * /*stream*/, void * user, const void * /*inputbuffer*/, void * outputbuffer, long nframes) +{ + synth_state *synth = (synth_state *)user; + synth_run_float(synth, (float*)outputbuffer, nframes); + return nframes; +} + +void synth_run_16bit(synth_state* synth, short* audiobuffer, long nframes) +{ + for(int c=0;c < synth->num_channels;++c) { + float freq = get_frequency(c); + float phase_inc = 2.0 * M_PI * freq / synth->sample_rate; + for(long n=0;n < nframes;++n) { + audiobuffer[n*synth->num_channels+c] = sin(synth->phase[c]) * VOLUME * 32767.0f; + synth->phase[c] += phase_inc; + } + } +} + +long data_cb_short(cubeb_stream * /*stream*/, void * user, const void * /*inputbuffer*/, void * outputbuffer, long nframes) +{ + synth_state *synth = (synth_state *)user; + synth_run_16bit(synth, (short*)outputbuffer, nframes); + return nframes; +} + +void state_cb(cubeb_stream * /*stream*/, void * /*user*/, cubeb_state /*state*/) +{ +} + +/* Our android backends don't support float, only int16. */ +int supports_float32(const char* backend_id) +{ + return (strcmp(backend_id, "opensl") != 0 && + strcmp(backend_id, "audiotrack") != 0); +} + +/* The WASAPI backend only supports float. */ +int supports_int16(const char* backend_id) +{ + return strcmp(backend_id, "wasapi") != 0; +} + +/* Some backends don't have code to deal with more than mono or stereo. */ +int supports_channel_count(const char* backend_id, int nchannels) +{ + return nchannels <= 2 || + (strcmp(backend_id, "opensl") != 0 && strcmp(backend_id, "audiotrack") != 0); +} + +int run_test(int num_channels, int sampling_rate, int is_float) +{ + int r = CUBEB_OK; + + cubeb *ctx = NULL; + synth_state* synth = NULL; + cubeb_stream *stream = NULL; + const char * backend_id = NULL; + + r = cubeb_init(&ctx, "Cubeb audio test: channels"); + if (r != CUBEB_OK) { + fprintf(stderr, "Error initializing cubeb library\n"); + goto cleanup; + } + + backend_id = cubeb_get_backend_id(ctx); + + if ((is_float && !supports_float32(backend_id)) || + (!is_float && !supports_int16(backend_id)) || + !supports_channel_count(backend_id, num_channels)) { + /* don't treat this as a test failure. */ + goto cleanup; + } + + fprintf(stderr, "Testing %d channel(s), %d Hz, %s (%s)\n", num_channels, sampling_rate, is_float ? "float" : "short", cubeb_get_backend_id(ctx)); + + cubeb_stream_params params; + params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE; + params.rate = sampling_rate; + params.channels = num_channels; + + synth = synth_create(params.channels, params.rate); + if (synth == NULL) { + fprintf(stderr, "Out of memory\n"); + goto cleanup; + } + + r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, ¶ms, + 4096, is_float ? data_cb_float : data_cb_short, state_cb, synth); + if (r != CUBEB_OK) { + fprintf(stderr, "Error initializing cubeb stream: %d\n", r); + goto cleanup; + } + + cubeb_stream_start(stream); + delay(200); + cubeb_stream_stop(stream); + +cleanup: + cubeb_stream_destroy(stream); + cubeb_destroy(ctx); + synth_destroy(synth); + + return r; +} + +int run_panning_volume_test(int is_float) +{ + int r = CUBEB_OK; + + cubeb *ctx = NULL; + synth_state* synth = NULL; + cubeb_stream *stream = NULL; + const char * backend_id = NULL; + + r = cubeb_init(&ctx, "Cubeb audio test"); + if (r != CUBEB_OK) { + fprintf(stderr, "Error initializing cubeb library\n"); + goto cleanup; + } + backend_id = cubeb_get_backend_id(ctx); + + if ((is_float && !supports_float32(backend_id)) || + (!is_float && !supports_int16(backend_id))) { + /* don't treat this as a test failure. */ + goto cleanup; + } + + cubeb_stream_params params; + params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE; + params.rate = 44100; + params.channels = 2; + + synth = synth_create(params.channels, params.rate); + if (synth == NULL) { + fprintf(stderr, "Out of memory\n"); + goto cleanup; + } + + r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, ¶ms, + 4096, is_float ? data_cb_float : data_cb_short, + state_cb, synth); + if (r != CUBEB_OK) { + fprintf(stderr, "Error initializing cubeb stream: %d\n", r); + goto cleanup; + } + + fprintf(stderr, "Testing: volume\n"); + for(int i=0;i <= 4; ++i) + { + fprintf(stderr, "Volume: %d%%\n", i*25); + + cubeb_stream_set_volume(stream, i/4.0f); + cubeb_stream_start(stream); + delay(400); + cubeb_stream_stop(stream); + delay(100); + } + + fprintf(stderr, "Testing: panning\n"); + for(int i=-4;i <= 4; ++i) + { + fprintf(stderr, "Panning: %.2f%%\n", i/4.0f); + + cubeb_stream_set_panning(stream, i/4.0f); + cubeb_stream_start(stream); + delay(400); + cubeb_stream_stop(stream); + delay(100); + } + +cleanup: + cubeb_stream_destroy(stream); + cubeb_destroy(ctx); + synth_destroy(synth); + + return r; +} + +void run_channel_rate_test() +{ + int channel_values[] = { + 1, + 2, + 3, + 4, + 6, + }; + + int freq_values[] = { + 16000, + 24000, + 44100, + 48000, + }; + + for(int j = 0; j < NELEMS(channel_values); ++j) { + for(int i = 0; i < NELEMS(freq_values); ++i) { + assert(channel_values[j] < MAX_NUM_CHANNELS); + fprintf(stderr, "--------------------------\n"); + assert(run_test(channel_values[j], freq_values[i], 0) == CUBEB_OK); + assert(run_test(channel_values[j], freq_values[i], 1) == CUBEB_OK); + } + } +} + + +int main(int /*argc*/, char * /*argv*/[]) +{ +#ifdef CUBEB_GECKO_BUILD + ScopedXPCOM xpcom("test_audio"); +#endif + + assert(run_panning_volume_test(0) == CUBEB_OK); + assert(run_panning_volume_test(1) == CUBEB_OK); + run_channel_rate_test(); + + return CUBEB_OK; +} diff --git a/media/libcubeb/tests/test_devices.cpp b/media/libcubeb/tests/test_devices.cpp new file mode 100644 index 000000000..9c502c0af --- /dev/null +++ b/media/libcubeb/tests/test_devices.cpp @@ -0,0 +1,162 @@ +/* + * Copyright © 2015 Haakon Sporsheim <haakon.sporsheim@telenordigital.com> + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ + +/* libcubeb enumerate device test/example. + * Prints out a list of devices enumerated. */ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "cubeb/cubeb.h" + + +static void +print_device_info(cubeb_device_info * info, FILE * f) +{ + char devfmts[64] = ""; + const char * devtype, * devstate, * devdeffmt; + + switch (info->type) { + case CUBEB_DEVICE_TYPE_INPUT: + devtype = "input"; + break; + case CUBEB_DEVICE_TYPE_OUTPUT: + devtype = "output"; + break; + case CUBEB_DEVICE_TYPE_UNKNOWN: + default: + devtype = "unknown?"; + break; + }; + + switch (info->state) { + case CUBEB_DEVICE_STATE_DISABLED: + devstate = "disabled"; + break; + case CUBEB_DEVICE_STATE_UNPLUGGED: + devstate = "unplugged"; + break; + case CUBEB_DEVICE_STATE_ENABLED: + devstate = "enabled"; + break; + default: + devstate = "unknown?"; + break; + }; + + switch (info->default_format) { + case CUBEB_DEVICE_FMT_S16LE: + devdeffmt = "S16LE"; + break; + case CUBEB_DEVICE_FMT_S16BE: + devdeffmt = "S16BE"; + break; + case CUBEB_DEVICE_FMT_F32LE: + devdeffmt = "F32LE"; + break; + case CUBEB_DEVICE_FMT_F32BE: + devdeffmt = "F32BE"; + break; + default: + devdeffmt = "unknown?"; + break; + }; + + if (info->format & CUBEB_DEVICE_FMT_S16LE) + strcat(devfmts, " S16LE"); + if (info->format & CUBEB_DEVICE_FMT_S16BE) + strcat(devfmts, " S16BE"); + if (info->format & CUBEB_DEVICE_FMT_F32LE) + strcat(devfmts, " F32LE"); + if (info->format & CUBEB_DEVICE_FMT_F32BE) + strcat(devfmts, " F32BE"); + + fprintf(f, + "dev: \"%s\"%s\n" + "\tName: \"%s\"\n" + "\tGroup: \"%s\"\n" + "\tVendor: \"%s\"\n" + "\tType: %s\n" + "\tState: %s\n" + "\tCh: %u\n" + "\tFormat: %s (0x%x) (default: %s)\n" + "\tRate: %u - %u (default: %u)\n" + "\tLatency: lo %ums, hi %ums\n", + info->device_id, info->preferred ? " (PREFERRED)" : "", + info->friendly_name, info->group_id, info->vendor_name, + devtype, devstate, info->max_channels, + (devfmts[0] == ' ') ? &devfmts[1] : devfmts, + (unsigned int)info->format, devdeffmt, + info->min_rate, info->max_rate, info->default_rate, + info->latency_lo_ms, info->latency_hi_ms); +} + +static void +print_device_collection(cubeb_device_collection * collection, FILE * f) +{ + uint32_t i; + + for (i = 0; i < collection->count; i++) + print_device_info(collection->device[i], f); +} + +static int +run_enumerate_devices(void) +{ + int r = CUBEB_OK; + cubeb * ctx = NULL; + cubeb_device_collection * collection = NULL; + + r = cubeb_init(&ctx, "Cubeb audio test"); + if (r != CUBEB_OK) { + fprintf(stderr, "Error initializing cubeb library\n"); + return r; + } + + fprintf(stdout, "Enumerating input devices for backend %s\n", + cubeb_get_backend_id(ctx)); + + r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_INPUT, &collection); + if (r != CUBEB_OK) { + fprintf(stderr, "Error enumerating devices %d\n", r); + goto cleanup; + } + + fprintf(stdout, "Found %u input devices\n", collection->count); + print_device_collection(collection, stdout); + cubeb_device_collection_destroy(collection); + + fprintf(stdout, "Enumerating output devices for backend %s\n", + cubeb_get_backend_id(ctx)); + + r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection); + if (r != CUBEB_OK) { + fprintf(stderr, "Error enumerating devices %d\n", r); + goto cleanup; + } + + fprintf(stdout, "Found %u output devices\n", collection->count); + print_device_collection(collection, stdout); + cubeb_device_collection_destroy(collection); + +cleanup: + cubeb_destroy(ctx); + return r; +} + +int main(int argc, char *argv[]) +{ + int ret; + + ret = run_enumerate_devices(); + + return ret; +} diff --git a/media/libcubeb/tests/test_duplex.cpp b/media/libcubeb/tests/test_duplex.cpp new file mode 100644 index 000000000..c5d02d782 --- /dev/null +++ b/media/libcubeb/tests/test_duplex.cpp @@ -0,0 +1,151 @@ +/* + * Copyright © 2016 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ + +/* libcubeb api/function test. Loops input back to output and check audio + * is flowing. */ +#ifdef NDEBUG +#undef NDEBUG +#endif +#define _XOPEN_SOURCE 600 +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include <assert.h> + +#include "cubeb/cubeb.h" +#include "common.h" +#ifdef CUBEB_GECKO_BUILD +#include "TestHarness.h" +#endif + +#define SAMPLE_FREQUENCY 48000 +#if (defined(_WIN32) || defined(__WIN32__)) +#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE +#define SILENT_SAMPLE 0.0f +#else +#define STREAM_FORMAT CUBEB_SAMPLE_S16LE +#define SILENT_SAMPLE 0 +#endif + +struct user_state +{ + bool seen_noise; +}; + + + +long data_cb(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes) +{ + user_state * u = reinterpret_cast<user_state*>(user); +#if (defined(_WIN32) || defined(__WIN32__)) + float *ib = (float *)inputbuffer; + float *ob = (float *)outputbuffer; +#else + short *ib = (short *)inputbuffer; + short *ob = (short *)outputbuffer; +#endif + bool seen_noise = false; + + if (stream == NULL || inputbuffer == NULL || outputbuffer == NULL) { + return CUBEB_ERROR; + } + + // Loop back: upmix the single input channel to the two output channels, + // checking if there is noise in the process. + long output_index = 0; + for (long i = 0; i < nframes; i++) { + if (ib[i] != SILENT_SAMPLE) { + seen_noise = true; + } + ob[output_index] = ob[output_index + 1] = ib[i]; + output_index += 2; + } + + u->seen_noise |= seen_noise; + + return nframes; +} + +void state_cb(cubeb_stream * stream, void * /*user*/, cubeb_state state) +{ + if (stream == NULL) + return; + + switch (state) { + case CUBEB_STATE_STARTED: + printf("stream started\n"); break; + case CUBEB_STATE_STOPPED: + printf("stream stopped\n"); break; + case CUBEB_STATE_DRAINED: + printf("stream drained\n"); break; + default: + printf("unknown stream state %d\n", state); + } + + return; +} + +int main(int /*argc*/, char * /*argv*/[]) +{ +#ifdef CUBEB_GECKO_BUILD + ScopedXPCOM xpcom("test_duplex"); +#endif + + cubeb *ctx; + cubeb_stream *stream; + cubeb_stream_params input_params; + cubeb_stream_params output_params; + int r; + user_state stream_state = { false }; + uint32_t latency_frames = 0; + + r = cubeb_init(&ctx, "Cubeb duplex example"); + if (r != CUBEB_OK) { + fprintf(stderr, "Error initializing cubeb library\n"); + return r; + } + + /* This test needs an available input device, skip it if this host does not + * have one. */ + if (!has_available_input_device(ctx)) { + return 0; + } + + /* typical user-case: mono input, stereo output, low latency. */ + input_params.format = STREAM_FORMAT; + input_params.rate = 48000; + input_params.channels = 1; + output_params.format = STREAM_FORMAT; + output_params.rate = 48000; + output_params.channels = 2; + + r = cubeb_get_min_latency(ctx, output_params, &latency_frames); + + if (r != CUBEB_OK) { + fprintf(stderr, "Could not get minimal latency\n"); + return r; + } + + r = cubeb_stream_init(ctx, &stream, "Cubeb duplex", + NULL, &input_params, NULL, &output_params, + latency_frames, data_cb, state_cb, &stream_state); + if (r != CUBEB_OK) { + fprintf(stderr, "Error initializing cubeb stream\n"); + return r; + } + + cubeb_stream_start(stream); + delay(500); + cubeb_stream_stop(stream); + + cubeb_stream_destroy(stream); + cubeb_destroy(ctx); + + assert(stream_state.seen_noise); + + return CUBEB_OK; +} diff --git a/media/libcubeb/tests/test_latency.cpp b/media/libcubeb/tests/test_latency.cpp new file mode 100644 index 000000000..0586b1a36 --- /dev/null +++ b/media/libcubeb/tests/test_latency.cpp @@ -0,0 +1,60 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif +#include <stdlib.h> +#include "cubeb/cubeb.h" +#include <assert.h> +#include <stdio.h> +#ifdef CUBEB_GECKO_BUILD +#include "TestHarness.h" +#endif + +#define LOG(msg) fprintf(stderr, "%s\n", msg); + +int main(int /*argc*/, char * /*argv*/[]) +{ +#ifdef CUBEB_GECKO_BUILD + ScopedXPCOM xpcom("test_latency"); +#endif + + cubeb * ctx = NULL; + int r; + uint32_t max_channels; + uint32_t preferred_rate; + uint32_t latency_frames; + + LOG("latency_test start"); + r = cubeb_init(&ctx, "Cubeb audio test"); + assert(r == CUBEB_OK && "Cubeb init failed."); + LOG("cubeb_init ok"); + + r = cubeb_get_max_channel_count(ctx, &max_channels); + assert(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED); + if (r == CUBEB_OK) { + assert(max_channels > 0 && "Invalid max channel count."); + LOG("cubeb_get_max_channel_count ok"); + } + + r = cubeb_get_preferred_sample_rate(ctx, &preferred_rate); + assert(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED); + if (r == CUBEB_OK) { + assert(preferred_rate > 0 && "Invalid preferred sample rate."); + LOG("cubeb_get_preferred_sample_rate ok"); + } + + cubeb_stream_params params = { + CUBEB_SAMPLE_FLOAT32NE, + preferred_rate, + max_channels + }; + r = cubeb_get_min_latency(ctx, params, &latency_frames); + assert(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED); + if (r == CUBEB_OK) { + assert(latency_frames > 0 && "Invalid minimal latency."); + LOG("cubeb_get_min_latency ok"); + } + + cubeb_destroy(ctx); + LOG("cubeb_destroy ok"); + return EXIT_SUCCESS; +} diff --git a/media/libcubeb/tests/test_record.cpp b/media/libcubeb/tests/test_record.cpp new file mode 100644 index 000000000..681e1641e --- /dev/null +++ b/media/libcubeb/tests/test_record.cpp @@ -0,0 +1,125 @@ +/* + * Copyright © 2016 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ + +/* libcubeb api/function test. Record the mic and check there is sound. */ +#ifdef NDEBUG +#undef NDEBUG +#endif +#define _XOPEN_SOURCE 600 +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include <assert.h> + +#include "cubeb/cubeb.h" +#include "common.h" +#ifdef CUBEB_GECKO_BUILD +#include "TestHarness.h" +#endif + +#define SAMPLE_FREQUENCY 48000 +#if (defined(_WIN32) || defined(__WIN32__)) +#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE +#else +#define STREAM_FORMAT CUBEB_SAMPLE_S16LE +#endif + +struct user_state +{ + bool seen_noise; +}; + +long data_cb(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes) +{ + user_state * u = reinterpret_cast<user_state*>(user); +#if STREAM_FORMAT != CUBEB_SAMPLE_FLOAT32LE + short *b = (short *)inputbuffer; +#else + float *b = (float *)inputbuffer; +#endif + + if (stream == NULL || inputbuffer == NULL || outputbuffer != NULL) { + return CUBEB_ERROR; + } + + bool seen_noise = false; + for (long i = 0; i < nframes; i++) { + if (b[i] != 0.0) { + seen_noise = true; + } + } + + u->seen_noise |= seen_noise; + + return nframes; +} + +void state_cb(cubeb_stream * stream, void * /*user*/, cubeb_state state) +{ + if (stream == NULL) + return; + + switch (state) { + case CUBEB_STATE_STARTED: + printf("stream started\n"); break; + case CUBEB_STATE_STOPPED: + printf("stream stopped\n"); break; + case CUBEB_STATE_DRAINED: + printf("stream drained\n"); break; + default: + printf("unknown stream state %d\n", state); + } + + return; +} + +int main(int /*argc*/, char * /*argv*/[]) +{ +#ifdef CUBEB_GECKO_BUILD + ScopedXPCOM xpcom("test_record"); +#endif + + cubeb *ctx; + cubeb_stream *stream; + cubeb_stream_params params; + int r; + user_state stream_state = { false }; + + r = cubeb_init(&ctx, "Cubeb record example"); + if (r != CUBEB_OK) { + fprintf(stderr, "Error initializing cubeb library\n"); + return r; + } + + /* This test needs an available input device, skip it if this host does not + * have one. */ + if (!has_available_input_device(ctx)) { + return 0; + } + + params.format = STREAM_FORMAT; + params.rate = SAMPLE_FREQUENCY; + params.channels = 1; + + r = cubeb_stream_init(ctx, &stream, "Cubeb record (mono)", NULL, ¶ms, NULL, nullptr, + 4096, data_cb, state_cb, &stream_state); + if (r != CUBEB_OK) { + fprintf(stderr, "Error initializing cubeb stream\n"); + return r; + } + + cubeb_stream_start(stream); + delay(500); + cubeb_stream_stop(stream); + + cubeb_stream_destroy(stream); + cubeb_destroy(ctx); + + assert(stream_state.seen_noise); + + return CUBEB_OK; +} diff --git a/media/libcubeb/tests/test_resampler.cpp b/media/libcubeb/tests/test_resampler.cpp new file mode 100644 index 000000000..7e62a3572 --- /dev/null +++ b/media/libcubeb/tests/test_resampler.cpp @@ -0,0 +1,554 @@ +/* + * Copyright © 2016 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +#ifdef NDEBUG +#undef NDEBUG +#endif +#include "cubeb_resampler_internal.h" +#include <assert.h> +#include <stdio.h> +#include <algorithm> +#include <iostream> + +/* Windows cmath USE_MATH_DEFINE thing... */ +const float PI = 3.14159265359f; + +/* Testing all sample rates is very long, so if THOROUGH_TESTING is not defined, + * only part of the test suite is ran. */ +#ifdef THOROUGH_TESTING +/* Some standard sample rates we're testing with. */ +const uint32_t sample_rates[] = { + 8000, + 16000, + 32000, + 44100, + 48000, + 88200, + 96000, + 192000 +}; +/* The maximum number of channels we're resampling. */ +const uint32_t max_channels = 2; +/* The minimum an maximum number of milliseconds we're resampling for. This is + * used to simulate the fact that the audio stream is resampled in chunks, + * because audio is delivered using callbacks. */ +const uint32_t min_chunks = 10; /* ms */ +const uint32_t max_chunks = 30; /* ms */ +const uint32_t chunk_increment = 1; + +#else + +const uint32_t sample_rates[] = { + 8000, + 44100, + 48000, +}; +const uint32_t max_channels = 2; +const uint32_t min_chunks = 10; /* ms */ +const uint32_t max_chunks = 30; /* ms */ +const uint32_t chunk_increment = 10; +#endif + +#define DUMP_ARRAYS +#ifdef DUMP_ARRAYS +/** + * Files produced by dump(...) can be converted to .wave files using: + * + * sox -c <channel_count> -r <rate> -e float -b 32 file.raw file.wav + * + * for floating-point audio, or: + * + * sox -c <channel_count> -r <rate> -e unsigned -b 16 file.raw file.wav + * + * for 16bit integer audio. + */ + +/* Use the correct implementation of fopen, depending on the platform. */ +void fopen_portable(FILE ** f, const char * name, const char * mode) +{ +#ifdef WIN32 + fopen_s(f, name, mode); +#else + *f = fopen(name, mode); +#endif +} + +template<typename T> +void dump(const char * name, T * frames, size_t count) +{ + FILE * file; + fopen_portable(&file, name, "wb"); + + if (!file) { + fprintf(stderr, "error opening %s\n", name); + return; + } + + if (count != fwrite(frames, sizeof(T), count, file)) { + fprintf(stderr, "error writing to %s\n", name); + } + fclose(file); +} +#else +template<typename T> +void dump(const char * name, T * frames, size_t count) +{ } +#endif + +// The more the ratio is far from 1, the more we accept a big error. +float epsilon_tweak_ratio(float ratio) +{ + return ratio >= 1 ? ratio : 1 / ratio; +} + +// Epsilon values for comparing resampled data to expected data. +// The bigger the resampling ratio is, the more lax we are about errors. +template<typename T> +T epsilon(float ratio); + +template<> +float epsilon(float ratio) { + return 0.08f * epsilon_tweak_ratio(ratio); +} + +template<> +int16_t epsilon(float ratio) { + return static_cast<int16_t>(10 * epsilon_tweak_ratio(ratio)); +} + +void test_delay_lines(uint32_t delay_frames, uint32_t channels, uint32_t chunk_ms) +{ + const size_t length_s = 2; + const size_t rate = 44100; + const size_t length_frames = rate * length_s; + delay_line<float> delay(delay_frames, channels); + auto_array<float> input; + auto_array<float> output; + uint32_t chunk_length = channels * chunk_ms * rate / 1000; + uint32_t output_offset = 0; + uint32_t channel = 0; + + /** Generate diracs every 100 frames, and check they are delayed. */ + input.push_silence(length_frames * channels); + for (uint32_t i = 0; i < input.length() - 1; i+=100) { + input.data()[i + channel] = 0.5; + channel = (channel + 1) % channels; + } + dump("input.raw", input.data(), input.length()); + while(input.length()) { + uint32_t to_pop = std::min<uint32_t>(input.length(), chunk_length * channels); + float * in = delay.input_buffer(to_pop / channels); + input.pop(in, to_pop); + delay.written(to_pop / channels); + output.push_silence(to_pop); + delay.output(output.data() + output_offset, to_pop / channels); + output_offset += to_pop; + } + + // Check the diracs have been shifted by `delay_frames` frames. + for (uint32_t i = 0; i < output.length() - delay_frames * channels + 1; i+=100) { + assert(output.data()[i + channel + delay_frames * channels] == 0.5); + channel = (channel + 1) % channels; + } + + dump("output.raw", output.data(), output.length()); +} +/** + * This takes sine waves with a certain `channels` count, `source_rate`, and + * resample them, by chunk of `chunk_duration` milliseconds, to `target_rate`. + * Then a sample-wise comparison is performed against a sine wave generated at + * the correct rate. + */ +template<typename T> +void test_resampler_one_way(uint32_t channels, uint32_t source_rate, uint32_t target_rate, float chunk_duration) +{ + size_t chunk_duration_in_source_frames = static_cast<uint32_t>(ceil(chunk_duration * source_rate / 1000.)); + float resampling_ratio = static_cast<float>(source_rate) / target_rate; + cubeb_resampler_speex_one_way<T> resampler(channels, source_rate, target_rate, 3); + auto_array<T> source(channels * source_rate * 10); + auto_array<T> destination(channels * target_rate * 10); + auto_array<T> expected(channels * target_rate * 10); + uint32_t phase_index = 0; + uint32_t offset = 0; + const uint32_t buf_len = 2; /* seconds */ + + // generate a sine wave in each channel, at the source sample rate + source.push_silence(channels * source_rate * buf_len); + while(offset != source.length()) { + float p = phase_index++ / static_cast<float>(source_rate); + for (uint32_t j = 0; j < channels; j++) { + source.data()[offset++] = 0.5 * sin(440. * 2 * PI * p); + } + } + + dump("input.raw", source.data(), source.length()); + + expected.push_silence(channels * target_rate * buf_len); + // generate a sine wave in each channel, at the target sample rate. + // Insert silent samples at the beginning to account for the resampler latency. + offset = resampler.latency() * channels; + for (uint32_t i = 0; i < offset; i++) { + expected.data()[i] = 0.0f; + } + phase_index = 0; + while (offset != expected.length()) { + float p = phase_index++ / static_cast<float>(target_rate); + for (uint32_t j = 0; j < channels; j++) { + expected.data()[offset++] = 0.5 * sin(440. * 2 * PI * p); + } + } + + dump("expected.raw", expected.data(), expected.length()); + + // resample by chunk + uint32_t write_offset = 0; + destination.push_silence(channels * target_rate * buf_len); + while (write_offset < destination.length()) + { + size_t output_frames = static_cast<uint32_t>(floor(chunk_duration_in_source_frames / resampling_ratio)); + uint32_t input_frames = resampler.input_needed_for_output(output_frames); + resampler.input(source.data(), input_frames); + source.pop(nullptr, input_frames * channels); + resampler.output(destination.data() + write_offset, + std::min(output_frames, (destination.length() - write_offset) / channels)); + write_offset += output_frames * channels; + } + + dump("output.raw", destination.data(), expected.length()); + + // compare, taking the latency into account + bool fuzzy_equal = true; + for (uint32_t i = resampler.latency() + 1; i < expected.length(); i++) { + float diff = fabs(expected.data()[i] - destination.data()[i]); + if (diff > epsilon<T>(resampling_ratio)) { + fprintf(stderr, "divergence at %d: %f %f (delta %f)\n", i, expected.data()[i], destination.data()[i], diff); + fuzzy_equal = false; + } + } + assert(fuzzy_equal); +} + +template<typename T> +cubeb_sample_format cubeb_format(); + +template<> +cubeb_sample_format cubeb_format<float>() +{ + return CUBEB_SAMPLE_FLOAT32NE; +} + +template<> +cubeb_sample_format cubeb_format<short>() +{ + return CUBEB_SAMPLE_S16NE; +} + +struct osc_state { + osc_state() + : input_phase_index(0) + , output_phase_index(0) + , output_offset(0) + , input_channels(0) + , output_channels(0) + {} + uint32_t input_phase_index; + uint32_t max_output_phase_index; + uint32_t output_phase_index; + uint32_t output_offset; + uint32_t input_channels; + uint32_t output_channels; + uint32_t output_rate; + uint32_t target_rate; + auto_array<float> input; + auto_array<float> output; +}; + +uint32_t fill_with_sine(float * buf, uint32_t rate, uint32_t channels, + uint32_t frames, uint32_t initial_phase) +{ + uint32_t offset = 0; + for (uint32_t i = 0; i < frames; i++) { + float p = initial_phase++ / static_cast<float>(rate); + for (uint32_t j = 0; j < channels; j++) { + buf[offset++] = 0.5 * sin(440. * 2 * PI * p); + } + } + return initial_phase; +} + +long data_cb(cubeb_stream * /*stm*/, void * user_ptr, + const void * input_buffer, void * output_buffer, long frame_count) +{ + osc_state * state = reinterpret_cast<osc_state*>(user_ptr); + const float * in = reinterpret_cast<const float*>(input_buffer); + float * out = reinterpret_cast<float*>(output_buffer); + + + state->input.push(in, frame_count * state->input_channels); + + /* Check how much output frames we need to write */ + uint32_t remaining = state->max_output_phase_index - state->output_phase_index; + uint32_t to_write = std::min<uint32_t>(remaining, frame_count); + state->output_phase_index = fill_with_sine(out, + state->target_rate, + state->output_channels, + to_write, + state->output_phase_index); + + return to_write; +} + +template<typename T> +bool array_fuzzy_equal(const auto_array<T>& lhs, const auto_array<T>& rhs, T epsi) +{ + uint32_t len = std::min(lhs.length(), rhs.length()); + + for (uint32_t i = 0; i < len; i++) { + if (fabs(lhs.at(i) - rhs.at(i)) > epsi) { + std::cout << "not fuzzy equal at index: " << i + << " lhs: " << lhs.at(i) << " rhs: " << rhs.at(i) + << " delta: " << fabs(lhs.at(i) - rhs.at(i)) + << " epsilon: "<< epsi << std::endl; + return false; + } + } + return true; +} + +template<typename T> +void test_resampler_duplex(uint32_t input_channels, uint32_t output_channels, + uint32_t input_rate, uint32_t output_rate, + uint32_t target_rate, float chunk_duration) +{ + cubeb_stream_params input_params; + cubeb_stream_params output_params; + osc_state state; + + input_params.format = output_params.format = cubeb_format<T>(); + state.input_channels = input_params.channels = input_channels; + state.output_channels = output_params.channels = output_channels; + input_params.rate = input_rate; + state.output_rate = output_params.rate = output_rate; + state.target_rate = target_rate; + long got; + + cubeb_resampler * resampler = + cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params, target_rate, + data_cb, (void*)&state, CUBEB_RESAMPLER_QUALITY_VOIP); + + long latency = cubeb_resampler_latency(resampler); + + const uint32_t duration_s = 2; + int32_t duration_frames = duration_s * target_rate; + uint32_t input_array_frame_count = ceil(chunk_duration * input_rate / 1000) + ceilf(static_cast<float>(input_rate) / target_rate) * 2; + uint32_t output_array_frame_count = chunk_duration * output_rate / 1000; + auto_array<float> input_buffer(input_channels * input_array_frame_count); + auto_array<float> output_buffer(output_channels * output_array_frame_count); + auto_array<float> expected_resampled_input(input_channels * duration_frames); + auto_array<float> expected_resampled_output(output_channels * output_rate * duration_s); + + state.max_output_phase_index = duration_s * target_rate; + + expected_resampled_input.push_silence(input_channels * duration_frames); + expected_resampled_output.push_silence(output_channels * output_rate * duration_s); + + /* expected output is a 440Hz sine wave at 16kHz */ + fill_with_sine(expected_resampled_input.data() + latency, + target_rate, input_channels, duration_frames - latency, 0); + /* expected output is a 440Hz sine wave at 32kHz */ + fill_with_sine(expected_resampled_output.data() + latency, + output_rate, output_channels, output_rate * duration_s - latency, 0); + + + while (state.output_phase_index != state.max_output_phase_index) { + uint32_t leftover_samples = input_buffer.length() * input_channels; + input_buffer.reserve(input_array_frame_count); + state.input_phase_index = fill_with_sine(input_buffer.data() + leftover_samples, + input_rate, + input_channels, + input_array_frame_count - leftover_samples, + state.input_phase_index); + long input_consumed = input_array_frame_count; + input_buffer.set_length(input_array_frame_count); + + got = cubeb_resampler_fill(resampler, + input_buffer.data(), &input_consumed, + output_buffer.data(), output_array_frame_count); + + /* handle leftover input */ + if (input_array_frame_count != static_cast<uint32_t>(input_consumed)) { + input_buffer.pop(nullptr, input_consumed * input_channels); + } else { + input_buffer.clear(); + } + + state.output.push(output_buffer.data(), got * state.output_channels); + } + + dump("input_expected.raw", expected_resampled_input.data(), expected_resampled_input.length()); + dump("output_expected.raw", expected_resampled_output.data(), expected_resampled_output.length()); + dump("input.raw", state.input.data(), state.input.length()); + dump("output.raw", state.output.data(), state.output.length()); + + assert(array_fuzzy_equal(state.input, expected_resampled_input, epsilon<T>(input_rate/target_rate))); + assert(array_fuzzy_equal(state.output, expected_resampled_output, epsilon<T>(output_rate/target_rate))); + + cubeb_resampler_destroy(resampler); +} + +#define array_size(x) (sizeof(x) / sizeof(x[0])) + +void test_resamplers_one_way() +{ + /* Test one way resamplers */ + for (uint32_t channels = 1; channels <= max_channels; channels++) { + for (uint32_t source_rate = 0; source_rate < array_size(sample_rates); source_rate++) { + for (uint32_t dest_rate = 0; dest_rate < array_size(sample_rates); dest_rate++) { + for (uint32_t chunk_duration = min_chunks; chunk_duration < max_chunks; chunk_duration+=chunk_increment) { + printf("one_way: channels: %d, source_rate: %d, dest_rate: %d, chunk_duration: %d\n", + channels, sample_rates[source_rate], sample_rates[dest_rate], chunk_duration); + test_resampler_one_way<float>(channels, sample_rates[source_rate], + sample_rates[dest_rate], chunk_duration); + } + } + } + } +} + +void test_resamplers_duplex() +{ + /* Test duplex resamplers */ + for (uint32_t input_channels = 1; input_channels <= max_channels; input_channels++) { + for (uint32_t output_channels = 1; output_channels <= max_channels; output_channels++) { + for (uint32_t source_rate_input = 0; source_rate_input < array_size(sample_rates); source_rate_input++) { + for (uint32_t source_rate_output = 0; source_rate_output < array_size(sample_rates); source_rate_output++) { + for (uint32_t dest_rate = 0; dest_rate < array_size(sample_rates); dest_rate++) { + for (uint32_t chunk_duration = min_chunks; chunk_duration < max_chunks; chunk_duration+=chunk_increment) { + printf("input channels:%d output_channels:%d input_rate:%d " + "output_rate:%d target_rate:%d chunk_ms:%d\n", + input_channels, output_channels, + sample_rates[source_rate_input], + sample_rates[source_rate_output], + sample_rates[dest_rate], + chunk_duration); + test_resampler_duplex<float>(input_channels, output_channels, + sample_rates[source_rate_input], + sample_rates[source_rate_output], + sample_rates[dest_rate], + chunk_duration); + } + } + } + } + } + } +} + +void test_delay_line() +{ + for (uint32_t channel = 1; channel <= 2; channel++) { + for (uint32_t delay_frames = 4; delay_frames <= 40; delay_frames+=chunk_increment) { + for (uint32_t chunk_size = 10; chunk_size <= 30; chunk_size++) { + printf("channel: %d, delay_frames: %d, chunk_size: %d\n", + channel, delay_frames, chunk_size); + test_delay_lines(delay_frames, channel, chunk_size); + } + } + } +} + +long test_output_only_noop_data_cb(cubeb_stream * /*stm*/, void * /*user_ptr*/, + const void * input_buffer, + void * output_buffer, long frame_count) +{ + assert(output_buffer); + assert(!input_buffer); + return frame_count; +} + +void test_output_only_noop() +{ + cubeb_stream_params output_params; + int target_rate; + + output_params.rate = 44100; + output_params.channels = 1; + output_params.format = CUBEB_SAMPLE_FLOAT32NE; + target_rate = output_params.rate; + + cubeb_resampler * resampler = + cubeb_resampler_create((cubeb_stream*)nullptr, nullptr, &output_params, target_rate, + test_output_only_noop_data_cb, nullptr, + CUBEB_RESAMPLER_QUALITY_VOIP); + + const long out_frames = 128; + float out_buffer[out_frames]; + long got; + + got = cubeb_resampler_fill(resampler, nullptr, nullptr, + out_buffer, out_frames); + + assert(got == out_frames); + + cubeb_resampler_destroy(resampler); +} + +long test_drain_data_cb(cubeb_stream * /*stm*/, void * /*user_ptr*/, + const void * input_buffer, + void * output_buffer, long frame_count) +{ + assert(output_buffer); + assert(!input_buffer); + return frame_count - 10; +} + +void test_resampler_drain() +{ + cubeb_stream_params output_params; + int target_rate; + + output_params.rate = 44100; + output_params.channels = 1; + output_params.format = CUBEB_SAMPLE_FLOAT32NE; + target_rate = 48000; + + cubeb_resampler * resampler = + cubeb_resampler_create((cubeb_stream*)nullptr, nullptr, &output_params, target_rate, + test_drain_data_cb, nullptr, + CUBEB_RESAMPLER_QUALITY_VOIP); + + const long out_frames = 128; + float out_buffer[out_frames]; + long got; + + do { + got = cubeb_resampler_fill(resampler, nullptr, nullptr, + out_buffer, out_frames); + } while (got == out_frames); + + /* If the above is not an infinite loop, the drain was a success, just mark + * this test as such. */ + assert(true); + + cubeb_resampler_destroy(resampler); +} + +int main() +{ + test_resamplers_one_way(); + test_delay_line(); + // This is disabled because the latency estimation in the resampler code is + // slightly off so we can generate expected vectors. + // test_resamplers_duplex(); + test_output_only_noop(); + test_resampler_drain(); + + return 0; +} diff --git a/media/libcubeb/tests/test_sanity.cpp b/media/libcubeb/tests/test_sanity.cpp new file mode 100644 index 000000000..77973ff15 --- /dev/null +++ b/media/libcubeb/tests/test_sanity.cpp @@ -0,0 +1,676 @@ +/* + * Copyright © 2011 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ +#ifdef NDEBUG +#undef NDEBUG +#endif +#define _XOPEN_SOURCE 600 +#include "cubeb/cubeb.h" +#include <assert.h> +#include <stdio.h> +#include <string.h> +#include <math.h> +#include "common.h" +#ifdef CUBEB_GECKO_BUILD +#include "TestHarness.h" +#endif + +#define BEGIN_TEST fprintf(stderr, "START %s\n", __func__) +#define END_TEST fprintf(stderr, "END %s\n", __func__) + +#define STREAM_RATE 44100 +#define STREAM_LATENCY 100 * STREAM_RATE / 1000 +#define STREAM_CHANNELS 1 +#if (defined(_WIN32) || defined(__WIN32__)) +#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE +#else +#define STREAM_FORMAT CUBEB_SAMPLE_S16LE +#endif + +template<typename T, size_t N> +constexpr size_t +ARRAY_LENGTH(T(&)[N]) +{ + return N; +} + +static int dummy; +static uint64_t total_frames_written; +static int delay_callback; + +static long +test_data_callback(cubeb_stream * stm, void * user_ptr, const void * /*inputbuffer*/, void * outputbuffer, long nframes) +{ + assert(stm && user_ptr == &dummy && outputbuffer && nframes > 0); +#if (defined(_WIN32) || defined(__WIN32__)) + memset(outputbuffer, 0, nframes * sizeof(float)); +#else + memset(outputbuffer, 0, nframes * sizeof(short)); +#endif + + total_frames_written += nframes; + if (delay_callback) { + delay(10); + } + return nframes; +} + +void +test_state_callback(cubeb_stream * /*stm*/, void * /*user_ptr*/, cubeb_state /*state*/) +{ +} + +static void +test_init_destroy_context(void) +{ + int r; + cubeb * ctx; + char const* backend_id; + + BEGIN_TEST; + + r = cubeb_init(&ctx, "test_sanity"); + assert(r == 0 && ctx); + + + backend_id = cubeb_get_backend_id(ctx); + assert(backend_id); + + fprintf(stderr, "Backend: %s\n", backend_id); + + cubeb_destroy(ctx); + + END_TEST; +} + +static void +test_init_destroy_multiple_contexts(void) +{ + size_t i; + int r; + cubeb * ctx[4]; + int order[4] = {2, 0, 3, 1}; + assert(ARRAY_LENGTH(ctx) == ARRAY_LENGTH(order)); + + BEGIN_TEST; + + for (i = 0; i < ARRAY_LENGTH(ctx); ++i) { + r = cubeb_init(&ctx[i], NULL); + assert(r == 0 && ctx[i]); + } + + /* destroy in a different order */ + for (i = 0; i < ARRAY_LENGTH(ctx); ++i) { + cubeb_destroy(ctx[order[i]]); + } + + END_TEST; +} + +static void +test_context_variables(void) +{ + int r; + cubeb * ctx; + uint32_t value; + cubeb_stream_params params; + + BEGIN_TEST; + + r = cubeb_init(&ctx, "test_context_variables"); + assert(r == 0 && ctx); + + params.channels = STREAM_CHANNELS; + params.format = STREAM_FORMAT; + params.rate = STREAM_RATE; +#if defined(__ANDROID__) + params.stream_type = CUBEB_STREAM_TYPE_MUSIC; +#endif + r = cubeb_get_min_latency(ctx, params, &value); + assert(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED); + if (r == CUBEB_OK) { + assert(value > 0); + } + + r = cubeb_get_preferred_sample_rate(ctx, &value); + assert(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED); + if (r == CUBEB_OK) { + assert(value > 0); + } + + cubeb_destroy(ctx); + + END_TEST; +} + +static void +test_init_destroy_stream(void) +{ + int r; + cubeb * ctx; + cubeb_stream * stream; + cubeb_stream_params params; + + BEGIN_TEST; + + r = cubeb_init(&ctx, "test_sanity"); + assert(r == 0 && ctx); + + params.format = STREAM_FORMAT; + params.rate = STREAM_RATE; + params.channels = STREAM_CHANNELS; +#if defined(__ANDROID__) + params.stream_type = CUBEB_STREAM_TYPE_MUSIC; +#endif + + r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY, + test_data_callback, test_state_callback, &dummy); + assert(r == 0 && stream); + + cubeb_stream_destroy(stream); + cubeb_destroy(ctx); + + END_TEST; +} + +static void +test_init_destroy_multiple_streams(void) +{ + size_t i; + int r; + cubeb * ctx; + cubeb_stream * stream[8]; + cubeb_stream_params params; + + BEGIN_TEST; + + r = cubeb_init(&ctx, "test_sanity"); + assert(r == 0 && ctx); + + params.format = STREAM_FORMAT; + params.rate = STREAM_RATE; + params.channels = STREAM_CHANNELS; +#if defined(__ANDROID__) + params.stream_type = CUBEB_STREAM_TYPE_MUSIC; +#endif + + for (i = 0; i < ARRAY_LENGTH(stream); ++i) { + r = cubeb_stream_init(ctx, &stream[i], "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY, + test_data_callback, test_state_callback, &dummy); + assert(r == 0); + assert(stream[i]); + } + + for (i = 0; i < ARRAY_LENGTH(stream); ++i) { + cubeb_stream_destroy(stream[i]); + } + + cubeb_destroy(ctx); + + END_TEST; +} + +static void +test_configure_stream(void) +{ + int r; + cubeb * ctx; + cubeb_stream * stream; + cubeb_stream_params params; + + BEGIN_TEST; + + r = cubeb_init(&ctx, "test_sanity"); + assert(r == 0 && ctx); + + params.format = STREAM_FORMAT; + params.rate = STREAM_RATE; + params.channels = 2; // panning +#if defined(__ANDROID__) + params.stream_type = CUBEB_STREAM_TYPE_MUSIC; +#endif + + r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY, + test_data_callback, test_state_callback, &dummy); + assert(r == 0 && stream); + + r = cubeb_stream_set_volume(stream, 1.0f); + assert(r == 0 || r == CUBEB_ERROR_NOT_SUPPORTED); + + r = cubeb_stream_set_panning(stream, 0.0f); + assert(r == 0 || r == CUBEB_ERROR_NOT_SUPPORTED); + + cubeb_stream_destroy(stream); + cubeb_destroy(ctx); + END_TEST; +} + +static void +test_init_start_stop_destroy_multiple_streams(int early, int delay_ms) +{ + size_t i; + int r; + cubeb * ctx; + cubeb_stream * stream[8]; + cubeb_stream_params params; + + BEGIN_TEST; + + r = cubeb_init(&ctx, "test_sanity"); + assert(r == 0 && ctx); + + params.format = STREAM_FORMAT; + params.rate = STREAM_RATE; + params.channels = STREAM_CHANNELS; +#if defined(__ANDROID__) + params.stream_type = CUBEB_STREAM_TYPE_MUSIC; +#endif + + for (i = 0; i < ARRAY_LENGTH(stream); ++i) { + r = cubeb_stream_init(ctx, &stream[i], "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY, + test_data_callback, test_state_callback, &dummy); + assert(r == 0); + assert(stream[i]); + if (early) { + r = cubeb_stream_start(stream[i]); + assert(r == 0); + } + } + + + if (!early) { + for (i = 0; i < ARRAY_LENGTH(stream); ++i) { + r = cubeb_stream_start(stream[i]); + assert(r == 0); + } + } + + if (delay_ms) { + delay(delay_ms); + } + + if (!early) { + for (i = 0; i < ARRAY_LENGTH(stream); ++i) { + r = cubeb_stream_stop(stream[i]); + assert(r == 0); + } + } + + for (i = 0; i < ARRAY_LENGTH(stream); ++i) { + if (early) { + r = cubeb_stream_stop(stream[i]); + assert(r == 0); + } + cubeb_stream_destroy(stream[i]); + } + + cubeb_destroy(ctx); + + END_TEST; +} + +static void +test_init_destroy_multiple_contexts_and_streams(void) +{ + size_t i, j; + int r; + cubeb * ctx[2]; + cubeb_stream * stream[8]; + cubeb_stream_params params; + size_t streams_per_ctx = ARRAY_LENGTH(stream) / ARRAY_LENGTH(ctx); + assert(ARRAY_LENGTH(ctx) * streams_per_ctx == ARRAY_LENGTH(stream)); + + BEGIN_TEST; + + params.format = STREAM_FORMAT; + params.rate = STREAM_RATE; + params.channels = STREAM_CHANNELS; +#if defined(__ANDROID__) + params.stream_type = CUBEB_STREAM_TYPE_MUSIC; +#endif + + for (i = 0; i < ARRAY_LENGTH(ctx); ++i) { + r = cubeb_init(&ctx[i], "test_sanity"); + assert(r == 0 && ctx[i]); + + for (j = 0; j < streams_per_ctx; ++j) { + r = cubeb_stream_init(ctx[i], &stream[i * streams_per_ctx + j], "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY, + test_data_callback, test_state_callback, &dummy); + assert(r == 0); + assert(stream[i * streams_per_ctx + j]); + } + } + + for (i = 0; i < ARRAY_LENGTH(ctx); ++i) { + for (j = 0; j < streams_per_ctx; ++j) { + cubeb_stream_destroy(stream[i * streams_per_ctx + j]); + } + cubeb_destroy(ctx[i]); + } + + END_TEST; +} + +static void +test_basic_stream_operations(void) +{ + int r; + cubeb * ctx; + cubeb_stream * stream; + cubeb_stream_params params; + uint64_t position; + + BEGIN_TEST; + + r = cubeb_init(&ctx, "test_sanity"); + assert(r == 0 && ctx); + + params.format = STREAM_FORMAT; + params.rate = STREAM_RATE; + params.channels = STREAM_CHANNELS; +#if defined(__ANDROID__) + params.stream_type = CUBEB_STREAM_TYPE_MUSIC; +#endif + + r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY, + test_data_callback, test_state_callback, &dummy); + assert(r == 0 && stream); + + /* position and volume before stream has started */ + r = cubeb_stream_get_position(stream, &position); + assert(r == 0 && position == 0); + + r = cubeb_stream_start(stream); + assert(r == 0); + + /* position and volume after while stream running */ + r = cubeb_stream_get_position(stream, &position); + assert(r == 0); + + r = cubeb_stream_stop(stream); + assert(r == 0); + + /* position and volume after stream has stopped */ + r = cubeb_stream_get_position(stream, &position); + assert(r == 0); + + cubeb_stream_destroy(stream); + cubeb_destroy(ctx); + + END_TEST; +} + +static void +test_stream_position(void) +{ + size_t i; + int r; + cubeb * ctx; + cubeb_stream * stream; + cubeb_stream_params params; + uint64_t position, last_position; + + BEGIN_TEST; + + total_frames_written = 0; + + r = cubeb_init(&ctx, "test_sanity"); + assert(r == 0 && ctx); + + params.format = STREAM_FORMAT; + params.rate = STREAM_RATE; + params.channels = STREAM_CHANNELS; +#if defined(__ANDROID__) + params.stream_type = CUBEB_STREAM_TYPE_MUSIC; +#endif + + r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY, + test_data_callback, test_state_callback, &dummy); + assert(r == 0 && stream); + + /* stream position should not advance before starting playback */ + r = cubeb_stream_get_position(stream, &position); + assert(r == 0 && position == 0); + + delay(500); + + r = cubeb_stream_get_position(stream, &position); + assert(r == 0 && position == 0); + + /* stream position should advance during playback */ + r = cubeb_stream_start(stream); + assert(r == 0); + + /* XXX let start happen */ + delay(500); + + /* stream should have prefilled */ + assert(total_frames_written > 0); + + r = cubeb_stream_get_position(stream, &position); + assert(r == 0); + last_position = position; + + delay(500); + + r = cubeb_stream_get_position(stream, &position); + assert(r == 0); + assert(position >= last_position); + last_position = position; + + /* stream position should not exceed total frames written */ + for (i = 0; i < 5; ++i) { + r = cubeb_stream_get_position(stream, &position); + assert(r == 0); + assert(position >= last_position); + assert(position <= total_frames_written); + last_position = position; + delay(500); + } + + /* test that the position is valid even when starting and + * stopping the stream. */ + for (i = 0; i < 5; ++i) { + r = cubeb_stream_stop(stream); + assert(r == 0); + r = cubeb_stream_get_position(stream, &position); + assert(r == 0); + assert(last_position < position); + last_position = position; + delay(500); + r = cubeb_stream_start(stream); + assert(r == 0); + delay(500); + } + + assert(last_position != 0); + + /* stream position should not advance after stopping playback */ + r = cubeb_stream_stop(stream); + assert(r == 0); + + /* XXX allow stream to settle */ + delay(500); + + r = cubeb_stream_get_position(stream, &position); + assert(r == 0); + last_position = position; + + delay(500); + + r = cubeb_stream_get_position(stream, &position); + assert(r == 0); + assert(position == last_position); + + cubeb_stream_destroy(stream); + cubeb_destroy(ctx); + + END_TEST; +} + +static int do_drain; +static int got_drain; + +static long +test_drain_data_callback(cubeb_stream * stm, void * user_ptr, const void * /*inputbuffer*/, void * outputbuffer, long nframes) +{ + assert(stm && user_ptr == &dummy && outputbuffer && nframes > 0); + if (do_drain == 1) { + do_drain = 2; + return 0; + } + /* once drain has started, callback must never be called again */ + assert(do_drain != 2); +#if (defined(_WIN32) || defined(__WIN32__)) + memset(outputbuffer, 0, nframes * sizeof(float)); +#else + memset(outputbuffer, 0, nframes * sizeof(short)); +#endif + total_frames_written += nframes; + return nframes; +} + +void +test_drain_state_callback(cubeb_stream * /*stm*/, void * /*user_ptr*/, cubeb_state state) +{ + if (state == CUBEB_STATE_DRAINED) { + assert(!got_drain); + got_drain = 1; + } +} + +static void +test_drain(void) +{ + int r; + cubeb * ctx; + cubeb_stream * stream; + cubeb_stream_params params; + uint64_t position; + + BEGIN_TEST; + + total_frames_written = 0; + + r = cubeb_init(&ctx, "test_sanity"); + assert(r == 0 && ctx); + + params.format = STREAM_FORMAT; + params.rate = STREAM_RATE; + params.channels = STREAM_CHANNELS; +#if defined(__ANDROID__) + params.stream_type = CUBEB_STREAM_TYPE_MUSIC; +#endif + + r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY, + test_drain_data_callback, test_drain_state_callback, &dummy); + assert(r == 0 && stream); + + r = cubeb_stream_start(stream); + assert(r == 0); + + delay(500); + + do_drain = 1; + + for (;;) { + r = cubeb_stream_get_position(stream, &position); + assert(r == 0); + if (got_drain) { + break; + } else { + assert(position <= total_frames_written); + } + delay(500); + } + + r = cubeb_stream_get_position(stream, &position); + assert(r == 0); + assert(got_drain); + + // Really, we should be able to rely on position reaching our final written frame, but + // for now let's make sure it doesn't continue beyond that point. + //assert(position <= total_frames_written); + + cubeb_stream_destroy(stream); + cubeb_destroy(ctx); + + END_TEST; +} + +int is_windows_7() +{ +#ifdef __MINGW32__ + printf("Warning: this test was built with MinGW.\n" + "MinGW does not contain necessary version checking infrastructure. Claiming to be Windows 7, even if we're not.\n"); + return 1; +#endif +#if (defined(_WIN32) || defined(__WIN32__)) && ( !defined(__MINGW32__)) + OSVERSIONINFOEX osvi; + DWORDLONG condition_mask = 0; + + ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + + // NT 6.1 is Windows 7 + osvi.dwMajorVersion = 6; + osvi.dwMinorVersion = 1; + + VER_SET_CONDITION(condition_mask, VER_MAJORVERSION, VER_EQUAL); + VER_SET_CONDITION(condition_mask, VER_MINORVERSION, VER_GREATER_EQUAL); + + return VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION, condition_mask); +#else + return 0; +#endif +} + +int +main(int /*argc*/, char * /*argv*/[]) +{ +#ifdef CUBEB_GECKO_BUILD + ScopedXPCOM xpcom("test_sanity"); +#endif + + test_init_destroy_context(); + test_init_destroy_multiple_contexts(); + test_context_variables(); + test_init_destroy_stream(); + test_init_destroy_multiple_streams(); + test_configure_stream(); + test_basic_stream_operations(); + test_stream_position(); + + /* Sometimes, when using WASAPI on windows 7 (vista and 8 are okay), and + * calling Activate a lot on an AudioClient, 0x800700b7 is returned. This is + * the HRESULT value for "Cannot create a file when that file already exists", + * and is not documented as a possible return value for this call. Hence, we + * try to limit the number of streams we create in this test. */ + if (!is_windows_7()) { + test_init_destroy_multiple_contexts_and_streams(); + + delay_callback = 0; + test_init_start_stop_destroy_multiple_streams(0, 0); + test_init_start_stop_destroy_multiple_streams(1, 0); + test_init_start_stop_destroy_multiple_streams(0, 150); + test_init_start_stop_destroy_multiple_streams(1, 150); + delay_callback = 1; + test_init_start_stop_destroy_multiple_streams(0, 0); + test_init_start_stop_destroy_multiple_streams(1, 0); + test_init_start_stop_destroy_multiple_streams(0, 150); + test_init_start_stop_destroy_multiple_streams(1, 150); + } + delay_callback = 0; + test_drain(); +/* + to implement: + test_eos_during_prefill(); + test_stream_destroy_pending_drain(); +*/ + printf("\n"); + + return 0; +} diff --git a/media/libcubeb/tests/test_tone.cpp b/media/libcubeb/tests/test_tone.cpp new file mode 100644 index 000000000..3c6e0ec54 --- /dev/null +++ b/media/libcubeb/tests/test_tone.cpp @@ -0,0 +1,149 @@ +/* + * Copyright © 2011 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ + +/* libcubeb api/function test. Plays a simple tone. */ +#ifdef NDEBUG +#undef NDEBUG +#endif +#define _XOPEN_SOURCE 600 +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include <assert.h> +#include <limits.h> + +#include "cubeb/cubeb.h" +#include "common.h" +#ifdef CUBEB_GECKO_BUILD +#include "TestHarness.h" +#endif + +#define SAMPLE_FREQUENCY 48000 +#if (defined(_WIN32) || defined(__WIN32__)) +#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE +#else +#define STREAM_FORMAT CUBEB_SAMPLE_S16LE +#endif + +/* store the phase of the generated waveform */ +struct cb_user_data { + long position; +}; + +long data_cb(cubeb_stream *stream, void *user, const void* /*inputbuffer*/, void *outputbuffer, long nframes) +{ + struct cb_user_data *u = (struct cb_user_data *)user; +#if (defined(_WIN32) || defined(__WIN32__)) + float *b = (float *)outputbuffer; +#else + short *b = (short *)outputbuffer; +#endif + float t1, t2; + int i; + + if (stream == NULL || u == NULL) + return CUBEB_ERROR; + + /* generate our test tone on the fly */ + for (i = 0; i < nframes; i++) { + /* North American dial tone */ + t1 = sin(2*M_PI*(i + u->position)*350/SAMPLE_FREQUENCY); + t2 = sin(2*M_PI*(i + u->position)*440/SAMPLE_FREQUENCY); +#if (defined(_WIN32) || defined(__WIN32__)) + b[i] = 0.5 * t1; + b[i] += 0.5 * t2; +#else + b[i] = (SHRT_MAX / 2) * t1; + b[i] += (SHRT_MAX / 2) * t2; +#endif + /* European dial tone */ + /* + t1 = sin(2*M_PI*(i + u->position)*425/SAMPLE_FREQUENCY); +#if (defined(_WIN32) || defined(__WIN32__)) + b[i] = t1; +#else + b[i] = SHRT_MAX * t1; +#endif + */ + } + /* remember our phase to avoid clicking on buffer transitions */ + /* we'll still click if position overflows */ + u->position += nframes; + + return nframes; +} + +void state_cb(cubeb_stream *stream, void *user, cubeb_state state) +{ + struct cb_user_data *u = (struct cb_user_data *)user; + + if (stream == NULL || u == NULL) + return; + + switch (state) { + case CUBEB_STATE_STARTED: + printf("stream started\n"); break; + case CUBEB_STATE_STOPPED: + printf("stream stopped\n"); break; + case CUBEB_STATE_DRAINED: + printf("stream drained\n"); break; + default: + printf("unknown stream state %d\n", state); + } + + return; +} + +int main(int /*argc*/, char * /*argv*/[]) +{ +#ifdef CUBEB_GECKO_BUILD + ScopedXPCOM xpcom("test_tone"); +#endif + + cubeb *ctx; + cubeb_stream *stream; + cubeb_stream_params params; + struct cb_user_data *user_data; + int r; + + r = cubeb_init(&ctx, "Cubeb tone example"); + if (r != CUBEB_OK) { + fprintf(stderr, "Error initializing cubeb library\n"); + return r; + } + + params.format = STREAM_FORMAT; + params.rate = SAMPLE_FREQUENCY; + params.channels = 1; + + user_data = (struct cb_user_data *) malloc(sizeof(*user_data)); + if (user_data == NULL) { + fprintf(stderr, "Error allocating user data\n"); + return CUBEB_ERROR; + } + user_data->position = 0; + + r = cubeb_stream_init(ctx, &stream, "Cubeb tone (mono)", NULL, NULL, NULL, ¶ms, + 4096, data_cb, state_cb, user_data); + if (r != CUBEB_OK) { + fprintf(stderr, "Error initializing cubeb stream\n"); + return r; + } + + cubeb_stream_start(stream); + delay(500); + cubeb_stream_stop(stream); + + cubeb_stream_destroy(stream); + cubeb_destroy(ctx); + + assert(user_data->position); + + free(user_data); + + return CUBEB_OK; +} diff --git a/media/libcubeb/tests/test_utils.cpp b/media/libcubeb/tests/test_utils.cpp new file mode 100644 index 000000000..f52cd3196 --- /dev/null +++ b/media/libcubeb/tests/test_utils.cpp @@ -0,0 +1,80 @@ +#include <cassert> +#include "cubeb_utils.h" + +int test_auto_array() +{ + auto_array<uint32_t> array; + auto_array<uint32_t> array2(10); + uint32_t a[10]; + + assert(array2.length() == 0); + assert(array2.capacity() == 10); + + + for (uint32_t i = 0; i < 10; i++) { + a[i] = i; + } + + assert(array.capacity() == 0); + assert(array.length() == 0); + + array.push(a, 10); + + assert(!array.reserve(9)); + + for (uint32_t i = 0; i < 10; i++) { + assert(array.data()[i] == i); + } + + assert(array.capacity() == 10); + assert(array.length() == 10); + + uint32_t b[10]; + + array.pop(b, 5); + + assert(array.capacity() == 10); + assert(array.length() == 5); + for (uint32_t i = 0; i < 5; i++) { + assert(b[i] == i); + assert(array.data()[i] == 5 + i); + } + uint32_t* bb = b + 5; + array.pop(bb, 5); + + assert(array.capacity() == 10); + assert(array.length() == 0); + for (uint32_t i = 0; i < 5; i++) { + assert(bb[i] == 5 + i); + } + + assert(!array.pop(nullptr, 1)); + + array.push(a, 10); + array.push(a, 10); + + for (uint32_t j = 0; j < 2; j++) { + for (uint32_t i = 0; i < 10; i++) { + assert(array.data()[10 * j + i] == i); + } + } + assert(array.length() == 20); + assert(array.capacity() == 20); + array.pop(nullptr, 5); + + for (uint32_t i = 0; i < 5; i++) { + assert(array.data()[i] == 5 + i); + } + + assert(array.length() == 15); + assert(array.capacity() == 20); + + return 0; +} + + +int main() +{ + test_auto_array(); + return 0; +} |