/* * 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 #include #include #include #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 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; }