michael@0: /* michael@0: * Copyright © 2011 Mozilla Foundation michael@0: * michael@0: * This program is made available under an ISC-style license. See the michael@0: * accompanying file LICENSE for details. michael@0: */ michael@0: #ifdef NDEBUG michael@0: #undef NDEBUG michael@0: #endif michael@0: #define _XOPEN_SOURCE 500 michael@0: #include "cubeb/cubeb.h" michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include "common.h" michael@0: michael@0: #if (defined(_WIN32) || defined(__WIN32__)) michael@0: #define __func__ __FUNCTION__ michael@0: #endif michael@0: michael@0: #define ARRAY_LENGTH(_x) (sizeof(_x) / sizeof(_x[0])) michael@0: #define BEGIN_TEST fprintf(stderr, "START %s\n", __func__); michael@0: #define END_TEST fprintf(stderr, "END %s\n", __func__); michael@0: michael@0: #define STREAM_LATENCY 100 michael@0: #define STREAM_RATE 44100 michael@0: #define STREAM_CHANNELS 1 michael@0: #define STREAM_FORMAT CUBEB_SAMPLE_S16LE michael@0: michael@0: static int dummy; michael@0: static uint64_t total_frames_written; michael@0: static int delay_callback; michael@0: michael@0: static long michael@0: test_data_callback(cubeb_stream * stm, void * user_ptr, void * p, long nframes) michael@0: { michael@0: assert(stm && user_ptr == &dummy && p && nframes > 0); michael@0: memset(p, 0, nframes * sizeof(short)); michael@0: total_frames_written += nframes; michael@0: if (delay_callback) { michael@0: delay(10); michael@0: } michael@0: return nframes; michael@0: } michael@0: michael@0: void michael@0: test_state_callback(cubeb_stream * stm, void * user_ptr, cubeb_state state) michael@0: { michael@0: } michael@0: michael@0: static void michael@0: test_init_destroy_context(void) michael@0: { michael@0: int r; michael@0: cubeb * ctx; michael@0: michael@0: BEGIN_TEST michael@0: michael@0: r = cubeb_init(&ctx, "test_sanity"); michael@0: assert(r == 0 && ctx); michael@0: michael@0: cubeb_destroy(ctx); michael@0: michael@0: END_TEST michael@0: } michael@0: michael@0: static void michael@0: test_init_destroy_multiple_contexts(void) michael@0: { michael@0: int i; michael@0: int r; michael@0: cubeb * ctx[4]; michael@0: michael@0: BEGIN_TEST michael@0: michael@0: for (i = 0; i < 4; ++i) { michael@0: r = cubeb_init(&ctx[i], NULL); michael@0: assert(r == 0 && ctx[i]); michael@0: } michael@0: michael@0: /* destroy in a different order */ michael@0: cubeb_destroy(ctx[2]); michael@0: cubeb_destroy(ctx[0]); michael@0: cubeb_destroy(ctx[3]); michael@0: cubeb_destroy(ctx[1]); michael@0: michael@0: END_TEST michael@0: } michael@0: michael@0: static void michael@0: test_init_destroy_stream(void) michael@0: { michael@0: int r; michael@0: cubeb * ctx; michael@0: cubeb_stream * stream; michael@0: cubeb_stream_params params; michael@0: michael@0: BEGIN_TEST michael@0: michael@0: r = cubeb_init(&ctx, "test_sanity"); michael@0: assert(r == 0 && ctx); michael@0: michael@0: params.format = STREAM_FORMAT; michael@0: params.rate = STREAM_RATE; michael@0: params.channels = STREAM_CHANNELS; michael@0: michael@0: r = cubeb_stream_init(ctx, &stream, "test", params, STREAM_LATENCY, michael@0: test_data_callback, test_state_callback, &dummy); michael@0: assert(r == 0 && stream); michael@0: michael@0: cubeb_stream_destroy(stream); michael@0: cubeb_destroy(ctx); michael@0: michael@0: END_TEST michael@0: } michael@0: michael@0: static void michael@0: test_init_destroy_multiple_streams(void) michael@0: { michael@0: int i; michael@0: int r; michael@0: cubeb * ctx; michael@0: cubeb_stream * stream[16]; michael@0: cubeb_stream_params params; michael@0: michael@0: BEGIN_TEST michael@0: michael@0: r = cubeb_init(&ctx, "test_sanity"); michael@0: assert(r == 0 && ctx); michael@0: michael@0: params.format = STREAM_FORMAT; michael@0: params.rate = STREAM_RATE; michael@0: params.channels = STREAM_CHANNELS; michael@0: michael@0: for (i = 0; i < 16; ++i) { michael@0: r = cubeb_stream_init(ctx, &stream[i], "test", params, STREAM_LATENCY, michael@0: test_data_callback, test_state_callback, &dummy); michael@0: assert(r == 0); michael@0: assert(stream[i]); michael@0: } michael@0: michael@0: for (i = 0; i < 16; ++i) { michael@0: cubeb_stream_destroy(stream[i]); michael@0: } michael@0: michael@0: cubeb_destroy(ctx); michael@0: michael@0: END_TEST michael@0: } michael@0: michael@0: static void michael@0: test_init_start_stop_destroy_multiple_streams(int early, int delay_ms) michael@0: { michael@0: int i; michael@0: int r; michael@0: cubeb * ctx; michael@0: cubeb_stream * stream[16]; michael@0: cubeb_stream_params params; michael@0: michael@0: BEGIN_TEST michael@0: michael@0: r = cubeb_init(&ctx, "test_sanity"); michael@0: assert(r == 0 && ctx); michael@0: michael@0: params.format = STREAM_FORMAT; michael@0: params.rate = STREAM_RATE; michael@0: params.channels = STREAM_CHANNELS; michael@0: michael@0: for (i = 0; i < 16; ++i) { michael@0: r = cubeb_stream_init(ctx, &stream[i], "test", params, STREAM_LATENCY, michael@0: test_data_callback, test_state_callback, &dummy); michael@0: assert(r == 0); michael@0: assert(stream[i]); michael@0: if (early) { michael@0: r = cubeb_stream_start(stream[i]); michael@0: assert(r == 0); michael@0: } michael@0: } michael@0: michael@0: michael@0: if (!early) { michael@0: for (i = 0; i < 16; ++i) { michael@0: r = cubeb_stream_start(stream[i]); michael@0: assert(r == 0); michael@0: } michael@0: } michael@0: michael@0: if (delay_ms) { michael@0: delay(delay_ms); michael@0: } michael@0: michael@0: if (!early) { michael@0: for (i = 0; i < 16; ++i) { michael@0: r = cubeb_stream_stop(stream[i]); michael@0: assert(r == 0); michael@0: } michael@0: } michael@0: michael@0: for (i = 0; i < 16; ++i) { michael@0: if (early) { michael@0: r = cubeb_stream_stop(stream[i]); michael@0: assert(r == 0); michael@0: } michael@0: cubeb_stream_destroy(stream[i]); michael@0: } michael@0: michael@0: cubeb_destroy(ctx); michael@0: michael@0: END_TEST michael@0: } michael@0: michael@0: static void michael@0: test_init_destroy_multiple_contexts_and_streams(void) michael@0: { michael@0: int i, j; michael@0: int r; michael@0: cubeb * ctx[4]; michael@0: cubeb_stream * stream[16]; michael@0: cubeb_stream_params params; michael@0: michael@0: BEGIN_TEST michael@0: michael@0: params.format = STREAM_FORMAT; michael@0: params.rate = STREAM_RATE; michael@0: params.channels = STREAM_CHANNELS; michael@0: michael@0: for (i = 0; i < 4; ++i) { michael@0: r = cubeb_init(&ctx[i], "test_sanity"); michael@0: assert(r == 0 && ctx[i]); michael@0: michael@0: for (j = 0; j < 4; ++j) { michael@0: r = cubeb_stream_init(ctx[i], &stream[i * 4 + j], "test", params, STREAM_LATENCY, michael@0: test_data_callback, test_state_callback, &dummy); michael@0: assert(r == 0); michael@0: assert(stream[i * 4 + j]); michael@0: } michael@0: } michael@0: michael@0: for (i = 0; i < 4; ++i) { michael@0: for (j = 0; j < 4; ++j) { michael@0: cubeb_stream_destroy(stream[i * 4 + j]); michael@0: } michael@0: cubeb_destroy(ctx[i]); michael@0: } michael@0: michael@0: END_TEST michael@0: } michael@0: michael@0: static void michael@0: test_basic_stream_operations(void) michael@0: { michael@0: int r; michael@0: cubeb * ctx; michael@0: cubeb_stream * stream; michael@0: cubeb_stream_params params; michael@0: uint64_t position; michael@0: michael@0: BEGIN_TEST michael@0: michael@0: r = cubeb_init(&ctx, "test_sanity"); michael@0: assert(r == 0 && ctx); michael@0: michael@0: params.format = STREAM_FORMAT; michael@0: params.rate = STREAM_RATE; michael@0: params.channels = STREAM_CHANNELS; michael@0: michael@0: r = cubeb_stream_init(ctx, &stream, "test", params, STREAM_LATENCY, michael@0: test_data_callback, test_state_callback, &dummy); michael@0: assert(r == 0 && stream); michael@0: michael@0: /* position and volume before stream has started */ michael@0: r = cubeb_stream_get_position(stream, &position); michael@0: assert(r == 0 && position == 0); michael@0: michael@0: r = cubeb_stream_start(stream); michael@0: assert(r == 0); michael@0: michael@0: /* position and volume after while stream running */ michael@0: r = cubeb_stream_get_position(stream, &position); michael@0: assert(r == 0); michael@0: michael@0: r = cubeb_stream_stop(stream); michael@0: assert(r == 0); michael@0: michael@0: /* position and volume after stream has stopped */ michael@0: r = cubeb_stream_get_position(stream, &position); michael@0: assert(r == 0); michael@0: michael@0: cubeb_stream_destroy(stream); michael@0: cubeb_destroy(ctx); michael@0: michael@0: END_TEST michael@0: } michael@0: michael@0: static void michael@0: test_stream_position(void) michael@0: { michael@0: int i; michael@0: int r; michael@0: cubeb * ctx; michael@0: cubeb_stream * stream; michael@0: cubeb_stream_params params; michael@0: uint64_t position, last_position; michael@0: michael@0: BEGIN_TEST michael@0: michael@0: total_frames_written = 0; michael@0: michael@0: r = cubeb_init(&ctx, "test_sanity"); michael@0: assert(r == 0 && ctx); michael@0: michael@0: params.format = STREAM_FORMAT; michael@0: params.rate = STREAM_RATE; michael@0: params.channels = STREAM_CHANNELS; michael@0: michael@0: r = cubeb_stream_init(ctx, &stream, "test", params, STREAM_LATENCY, michael@0: test_data_callback, test_state_callback, &dummy); michael@0: assert(r == 0 && stream); michael@0: michael@0: /* stream position should not advance before starting playback */ michael@0: r = cubeb_stream_get_position(stream, &position); michael@0: assert(r == 0 && position == 0); michael@0: michael@0: delay(500); michael@0: michael@0: r = cubeb_stream_get_position(stream, &position); michael@0: assert(r == 0 && position == 0); michael@0: michael@0: /* stream position should advance during playback */ michael@0: r = cubeb_stream_start(stream); michael@0: assert(r == 0); michael@0: michael@0: /* XXX let start happen */ michael@0: delay(500); michael@0: michael@0: /* stream should have prefilled */ michael@0: assert(total_frames_written > 0); michael@0: michael@0: r = cubeb_stream_get_position(stream, &position); michael@0: assert(r == 0); michael@0: last_position = position; michael@0: michael@0: delay(500); michael@0: michael@0: r = cubeb_stream_get_position(stream, &position); michael@0: assert(r == 0); michael@0: assert(position >= last_position); michael@0: last_position = position; michael@0: michael@0: /* stream position should not exceed total frames written */ michael@0: for (i = 0; i < 5; ++i) { michael@0: r = cubeb_stream_get_position(stream, &position); michael@0: assert(r == 0); michael@0: assert(position >= last_position); michael@0: assert(position <= total_frames_written); michael@0: last_position = position; michael@0: delay(500); michael@0: } michael@0: michael@0: assert(last_position != 0); michael@0: michael@0: /* stream position should not advance after stopping playback */ michael@0: r = cubeb_stream_stop(stream); michael@0: assert(r == 0); michael@0: michael@0: /* XXX allow stream to settle */ michael@0: delay(500); michael@0: michael@0: r = cubeb_stream_get_position(stream, &position); michael@0: assert(r == 0); michael@0: last_position = position; michael@0: michael@0: delay(500); michael@0: michael@0: r = cubeb_stream_get_position(stream, &position); michael@0: assert(r == 0); michael@0: assert(position == last_position); michael@0: michael@0: cubeb_stream_destroy(stream); michael@0: cubeb_destroy(ctx); michael@0: michael@0: END_TEST michael@0: } michael@0: michael@0: static int do_drain; michael@0: static int got_drain; michael@0: michael@0: static long michael@0: test_drain_data_callback(cubeb_stream * stm, void * user_ptr, void * p, long nframes) michael@0: { michael@0: assert(stm && user_ptr == &dummy && p && nframes > 0); michael@0: if (do_drain == 1) { michael@0: do_drain = 2; michael@0: return 0; michael@0: } michael@0: /* once drain has started, callback must never be called again */ michael@0: assert(do_drain != 2); michael@0: memset(p, 0, nframes * sizeof(short)); michael@0: total_frames_written += nframes; michael@0: return nframes; michael@0: } michael@0: michael@0: void michael@0: test_drain_state_callback(cubeb_stream * stm, void * user_ptr, cubeb_state state) michael@0: { michael@0: if (state == CUBEB_STATE_DRAINED) { michael@0: assert(!got_drain); michael@0: got_drain = 1; michael@0: } michael@0: } michael@0: michael@0: static void michael@0: test_drain(void) michael@0: { michael@0: int r; michael@0: cubeb * ctx; michael@0: cubeb_stream * stream; michael@0: cubeb_stream_params params; michael@0: uint64_t position; michael@0: michael@0: BEGIN_TEST michael@0: michael@0: total_frames_written = 0; michael@0: michael@0: r = cubeb_init(&ctx, "test_sanity"); michael@0: assert(r == 0 && ctx); michael@0: michael@0: params.format = STREAM_FORMAT; michael@0: params.rate = STREAM_RATE; michael@0: params.channels = STREAM_CHANNELS; michael@0: michael@0: r = cubeb_stream_init(ctx, &stream, "test", params, STREAM_LATENCY, michael@0: test_drain_data_callback, test_drain_state_callback, &dummy); michael@0: assert(r == 0 && stream); michael@0: michael@0: r = cubeb_stream_start(stream); michael@0: assert(r == 0); michael@0: michael@0: delay(500); michael@0: michael@0: do_drain = 1; michael@0: michael@0: for (;;) { michael@0: r = cubeb_stream_get_position(stream, &position); michael@0: assert(r == 0); michael@0: if (got_drain) { michael@0: break; michael@0: } else { michael@0: uint32_t i, skip = 0; michael@0: /* Latency passed to cubeb_stream_init is not really honored on OSX, michael@0: win32/winmm and android, skip this test. */ michael@0: const char * backend_id = cubeb_get_backend_id(ctx); michael@0: const char * latency_not_honored_bakends[] = { michael@0: "audiounit", michael@0: "winmm", michael@0: "audiotrack", michael@0: "opensl" michael@0: }; michael@0: michael@0: for (i = 0; i < ARRAY_LENGTH(latency_not_honored_bakends); i++) { michael@0: if (!strcmp(backend_id, latency_not_honored_bakends[i])) { michael@0: skip = 1; michael@0: } michael@0: } michael@0: if (!skip) { michael@0: /* Position should roughly be equal to the number of written frames. We michael@0: * need to take the latency into account. */ michael@0: int latency = (STREAM_LATENCY * STREAM_RATE) / 1000; michael@0: assert(position + latency <= total_frames_written); michael@0: } michael@0: } michael@0: delay(500); michael@0: } michael@0: michael@0: r = cubeb_stream_get_position(stream, &position); michael@0: assert(r == 0); michael@0: assert(got_drain); michael@0: michael@0: // Disabled due to failures in the ALSA backend. michael@0: //assert(position == total_frames_written); michael@0: michael@0: cubeb_stream_destroy(stream); michael@0: cubeb_destroy(ctx); michael@0: michael@0: END_TEST michael@0: } michael@0: michael@0: int is_windows_7() michael@0: { michael@0: #if (defined(_WIN32) || defined(__WIN32__)) michael@0: OSVERSIONINFOEX osvi; michael@0: DWORDLONG condition_mask = 0; michael@0: michael@0: ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX)); michael@0: osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); michael@0: michael@0: // NT 6.1 is Windows 7 michael@0: osvi.dwMajorVersion = 6; michael@0: osvi.dwMinorVersion = 1; michael@0: michael@0: VER_SET_CONDITION(condition_mask, VER_MAJORVERSION, VER_EQUAL); michael@0: VER_SET_CONDITION(condition_mask, VER_MINORVERSION, VER_EQUAL); michael@0: michael@0: return VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION, condition_mask); michael@0: #else michael@0: return 0; michael@0: #endif michael@0: } michael@0: michael@0: int michael@0: main(int argc, char * argv[]) michael@0: { michael@0: test_init_destroy_context(); michael@0: test_init_destroy_multiple_contexts(); michael@0: test_init_destroy_stream(); michael@0: test_init_destroy_multiple_streams(); michael@0: test_basic_stream_operations(); michael@0: test_stream_position(); michael@0: michael@0: /* Sometimes, when using WASAPI on windows 7 (vista and 8 are okay), and michael@0: * calling Activate a lot on an AudioClient, 0x800700b7 is returned. This is michael@0: * the HRESULT value for "Cannot create a file when that file already exists", michael@0: * and is not documented as a possible return value for this call. Hence, we michael@0: * try to limit the number of streams we create in this test. */ michael@0: if (!is_windows_7()) { michael@0: test_init_destroy_multiple_contexts_and_streams(); michael@0: michael@0: delay_callback = 0; michael@0: test_init_start_stop_destroy_multiple_streams(0, 0); michael@0: test_init_start_stop_destroy_multiple_streams(1, 0); michael@0: test_init_start_stop_destroy_multiple_streams(0, 150); michael@0: test_init_start_stop_destroy_multiple_streams(1, 150); michael@0: delay_callback = 1; michael@0: test_init_start_stop_destroy_multiple_streams(0, 0); michael@0: test_init_start_stop_destroy_multiple_streams(1, 0); michael@0: test_init_start_stop_destroy_multiple_streams(0, 150); michael@0: test_init_start_stop_destroy_multiple_streams(1, 150); michael@0: } michael@0: delay_callback = 0; michael@0: test_drain(); michael@0: /* michael@0: to implement: michael@0: test_eos_during_prefill(); michael@0: test_stream_destroy_pending_drain(); michael@0: */ michael@0: printf("\n"); michael@0: return 0; michael@0: }