summaryrefslogtreecommitdiffstats
path: root/media/libcubeb/test
diff options
context:
space:
mode:
Diffstat (limited to 'media/libcubeb/test')
-rw-r--r--media/libcubeb/test/README.md13
-rw-r--r--media/libcubeb/test/common.h181
-rw-r--r--media/libcubeb/test/test_audio.cpp243
-rw-r--r--media/libcubeb/test/test_callback_ret.cpp239
-rw-r--r--media/libcubeb/test/test_deadlock.cpp262
-rw-r--r--media/libcubeb/test/test_device_changed_callback.cpp121
-rw-r--r--media/libcubeb/test/test_devices.cpp262
-rw-r--r--media/libcubeb/test/test_duplex.cpp360
-rw-r--r--media/libcubeb/test/test_latency.cpp47
-rw-r--r--media/libcubeb/test/test_logging.cpp193
-rw-r--r--media/libcubeb/test/test_loopback.cpp596
-rw-r--r--media/libcubeb/test/test_overload_callback.cpp101
-rw-r--r--media/libcubeb/test/test_record.cpp119
-rw-r--r--media/libcubeb/test/test_resampler.cpp1089
-rw-r--r--media/libcubeb/test/test_ring_array.cpp73
-rw-r--r--media/libcubeb/test/test_ring_buffer.cpp229
-rw-r--r--media/libcubeb/test/test_sanity.cpp698
-rw-r--r--media/libcubeb/test/test_tone.cpp124
-rw-r--r--media/libcubeb/test/test_triple_buffer.cpp67
-rw-r--r--media/libcubeb/test/test_utils.cpp72
20 files changed, 5089 insertions, 0 deletions
diff --git a/media/libcubeb/test/README.md b/media/libcubeb/test/README.md
new file mode 100644
index 0000000000..7318263989
--- /dev/null
+++ b/media/libcubeb/test/README.md
@@ -0,0 +1,13 @@
+Notes on writing tests.
+
+The googletest submodule is currently at 1.6 rather than the latest, and should
+only be updated to track the version used in Gecko to make test compatibility
+easier.
+
+Always #include "gtest/gtest.h" before *anything* else.
+
+All tests should be part of the "cubeb" test case, e.g. TEST(cubeb, my_test).
+
+Tests are built stand-alone in cubeb, but built as a single unit in Gecko, so
+you must use unique names for globally visible items in each test, e.g. rather
+than state_cb use state_cb_my_test.
diff --git a/media/libcubeb/test/common.h b/media/libcubeb/test/common.h
new file mode 100644
index 0000000000..76d318cd98
--- /dev/null
+++ b/media/libcubeb/test/common.h
@@ -0,0 +1,181 @@
+/*
+ * Copyright © 2013 Sebastien Alaiwan
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#if !defined(TEST_COMMON)
+#define TEST_COMMON
+
+#if defined( _WIN32)
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include <objbase.h>
+#include <windows.h>
+#else
+#include <unistd.h>
+#endif
+
+#include <cstdarg>
+#include "cubeb/cubeb.h"
+#include "cubeb_mixer.h"
+
+template<typename T, size_t N>
+constexpr size_t
+ARRAY_LENGTH(T(&)[N])
+{
+ return N;
+}
+
+inline 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
+
+typedef struct {
+ char const * name;
+ unsigned int const channels;
+ uint32_t const layout;
+} layout_info;
+
+struct backend_caps {
+ const char* id;
+ const int input_capabilities;
+};
+
+
+// This static table allows knowing if a backend has audio input capabilities.
+// We don't rely on opening a stream and checking if it works, because this
+// would make the test skip the tests that make use of audio input, if a
+// particular backend has a bug that causes a failure during audio input stream
+// creation
+static backend_caps backend_capabilities[] = {
+ {"sun", 1},
+ {"wasapi", 1},
+ {"kai", 1},
+ {"audiounit", 1},
+ {"audiotrack", 0},
+ {"opensl", 1},
+ {"aaudio", 1},
+ {"jack", 1},
+ {"pulse", 1},
+ {"sndio", 1},
+ {"oss", 1},
+ {"winmm", 0},
+ {"alsa", 1},
+};
+
+inline int can_run_audio_input_test(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");
+ cubeb_device_collection_destroy(ctx, &devices);
+ 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");
+ }
+
+ cubeb_device_collection_destroy(ctx, &devices);
+
+ int backend_has_input_capabilities;
+ const char * backend_id = cubeb_get_backend_id(ctx);
+ for (uint32_t i = 0; i < sizeof(backend_capabilities) / sizeof(backend_caps); i++) {
+ if (strcmp(backend_capabilities[i].id, backend_id) == 0) {
+ backend_has_input_capabilities = backend_capabilities[i].input_capabilities;
+ }
+ }
+
+ return !!input_device_available && !!backend_has_input_capabilities;
+}
+
+inline void print_log(const char * msg, ...)
+{
+ va_list args;
+ va_start(args, msg);
+ vprintf(msg, args);
+ va_end(args);
+}
+
+/** Initialize cubeb with backend override.
+ * Create call cubeb_init passing value for CUBEB_BACKEND env var as
+ * override. */
+inline int common_init(cubeb ** ctx, char const * ctx_name)
+{
+#ifdef ENABLE_NORMAL_LOG
+ if (cubeb_set_log_callback(CUBEB_LOG_NORMAL, print_log) != CUBEB_OK) {
+ fprintf(stderr, "Set normal log callback failed\n");
+ }
+#endif
+
+#ifdef ENABLE_VERBOSE_LOG
+ if (cubeb_set_log_callback(CUBEB_LOG_VERBOSE, print_log) != CUBEB_OK) {
+ fprintf(stderr, "Set verbose log callback failed\n");
+ }
+#endif
+
+ int r;
+ char const * backend;
+ char const * ctx_backend;
+
+ backend = getenv("CUBEB_BACKEND");
+ r = cubeb_init(ctx, ctx_name, backend);
+ if (r == CUBEB_OK && backend) {
+ ctx_backend = cubeb_get_backend_id(*ctx);
+ if (strcmp(backend, ctx_backend) != 0) {
+ fprintf(stderr, "Requested backend `%s', got `%s'\n",
+ backend, ctx_backend);
+ }
+ }
+
+ return r;
+}
+
+#if defined( _WIN32)
+class TestEnvironment : public ::testing::Environment {
+public:
+ void SetUp() override {
+ hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
+ }
+
+ void TearDown() override {
+ if (SUCCEEDED(hr)) {
+ CoUninitialize();
+ }
+ }
+
+private:
+ HRESULT hr;
+};
+
+::testing::Environment* const foo_env = ::testing::AddGlobalTestEnvironment(new TestEnvironment);
+#endif
+
+#endif /* TEST_COMMON */
diff --git a/media/libcubeb/test/test_audio.cpp b/media/libcubeb/test/test_audio.cpp
new file mode 100644
index 0000000000..bc4b435712
--- /dev/null
+++ b/media/libcubeb/test/test_audio.cpp
@@ -0,0 +1,243 @@
+/*
+ * 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. */
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <memory>
+#include <string.h>
+#include "cubeb/cubeb.h"
+#include <string>
+
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+using namespace std;
+
+#define MAX_NUM_CHANNELS 32
+#define VOLUME 0.2
+
+float get_frequency(int channel_index)
+{
+ return 220.0f * (channel_index+1);
+}
+
+template<typename T> T ConvertSample(double input);
+template<> float ConvertSample(double input) { return input; }
+template<> short ConvertSample(double input) { return short(input * 32767.0f); }
+
+/* store the phase of the generated waveform */
+struct synth_state {
+ synth_state(int num_channels_, float sample_rate_)
+ : num_channels(num_channels_),
+ sample_rate(sample_rate_)
+ {
+ for(int i=0;i < MAX_NUM_CHANNELS;++i)
+ phase[i] = 0.0f;
+ }
+
+ template<typename T>
+ void run(T* audiobuffer, long nframes)
+ {
+ for(int c=0;c < num_channels;++c) {
+ float freq = get_frequency(c);
+ float phase_inc = 2.0 * M_PI * freq / sample_rate;
+ for(long n=0;n < nframes;++n) {
+ audiobuffer[n*num_channels+c] = ConvertSample<T>(sin(phase[c]) * VOLUME);
+ phase[c] += phase_inc;
+ }
+ }
+ }
+
+private:
+ int num_channels;
+ float phase[MAX_NUM_CHANNELS];
+ float sample_rate;
+};
+
+template<typename T>
+long data_cb(cubeb_stream * /*stream*/, void * user, const void * /*inputbuffer*/, void * outputbuffer, long nframes)
+{
+ synth_state *synth = (synth_state *)user;
+ synth->run((T*)outputbuffer, nframes);
+ return nframes;
+}
+
+void state_cb_audio(cubeb_stream * /*stream*/, void * /*user*/, cubeb_state /*state*/)
+{
+}
+
+/* Our android backends don't support float, only int16. */
+int supports_float32(string backend_id)
+{
+ return backend_id != "opensl"
+ && backend_id != "audiotrack";
+}
+
+/* Some backends don't have code to deal with more than mono or stereo. */
+int supports_channel_count(string backend_id, int nchannels)
+{
+ return nchannels <= 2 ||
+ (backend_id != "opensl" && backend_id != "audiotrack");
+}
+
+int run_test(int num_channels, int sampling_rate, int is_float)
+{
+ int r = CUBEB_OK;
+
+ cubeb *ctx = NULL;
+
+ r = common_init(&ctx, "Cubeb audio test: channels");
+ if (r != CUBEB_OK) {
+ fprintf(stderr, "Error initializing cubeb library\n");
+ return r;
+ }
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ const char * backend_id = cubeb_get_backend_id(ctx);
+
+ if ((is_float && !supports_float32(backend_id)) ||
+ !supports_channel_count(backend_id, num_channels)) {
+ /* don't treat this as a test failure. */
+ return CUBEB_OK;
+ }
+
+ 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;
+ params.layout = CUBEB_LAYOUT_UNDEFINED;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ synth_state synth(params.channels, params.rate);
+
+ cubeb_stream *stream = NULL;
+ r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, &params,
+ 4096, is_float ? &data_cb<float> : &data_cb<short>, state_cb_audio, &synth);
+ if (r != CUBEB_OK) {
+ fprintf(stderr, "Error initializing cubeb stream: %d\n", r);
+ return r;
+ }
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(stream);
+ delay(200);
+ cubeb_stream_stop(stream);
+
+ return r;
+}
+
+int run_volume_test(int is_float)
+{
+ int r = CUBEB_OK;
+
+ cubeb *ctx = NULL;
+
+ r = common_init(&ctx, "Cubeb audio test");
+ if (r != CUBEB_OK) {
+ fprintf(stderr, "Error initializing cubeb library\n");
+ return r;
+ }
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ const char * backend_id = cubeb_get_backend_id(ctx);
+
+ if ((is_float && !supports_float32(backend_id))) {
+ /* don't treat this as a test failure. */
+ return CUBEB_OK;
+ }
+
+ cubeb_stream_params params;
+ params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE;
+ params.rate = 44100;
+ params.channels = 2;
+ params.layout = CUBEB_LAYOUT_STEREO;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ synth_state synth(params.channels, params.rate);
+
+ cubeb_stream *stream = NULL;
+ r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, &params,
+ 4096, is_float ? &data_cb<float> : &data_cb<short>,
+ state_cb_audio, &synth);
+ if (r != CUBEB_OK) {
+ fprintf(stderr, "Error initializing cubeb stream: %d\n", r);
+ return r;
+ }
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ 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);
+ }
+
+ return r;
+}
+
+TEST(cubeb, run_volume_test_short)
+{
+ ASSERT_EQ(run_volume_test(0), CUBEB_OK);
+}
+
+TEST(cubeb, run_volume_test_float)
+{
+ ASSERT_EQ(run_volume_test(1), CUBEB_OK);
+}
+
+TEST(cubeb, run_channel_rate_test)
+{
+ unsigned int channel_values[] = {
+ 1,
+ 2,
+ 3,
+ 4,
+ 6,
+ };
+
+ int freq_values[] = {
+ 16000,
+ 24000,
+ 44100,
+ 48000,
+ };
+
+ for(auto channels : channel_values) {
+ for(auto freq : freq_values) {
+ ASSERT_TRUE(channels < MAX_NUM_CHANNELS);
+ fprintf(stderr, "--------------------------\n");
+ ASSERT_EQ(run_test(channels, freq, 0), CUBEB_OK);
+ ASSERT_EQ(run_test(channels, freq, 1), CUBEB_OK);
+ }
+ }
+}
+
+
+#undef MAX_NUM_CHANNELS
+#undef VOLUME
diff --git a/media/libcubeb/test/test_callback_ret.cpp b/media/libcubeb/test/test_callback_ret.cpp
new file mode 100644
index 0000000000..49676f7fa2
--- /dev/null
+++ b/media/libcubeb/test/test_callback_ret.cpp
@@ -0,0 +1,239 @@
+/*
+* Copyright � 2017 Mozilla Foundation
+*
+* This program is made available under an ISC-style license. See the
+* accompanying file LICENSE for details.
+*/
+
+/* libcubeb api/function test. Test that different return values from user
+ specified callbacks are handled correctly. */
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include <memory>
+#include <atomic>
+#include <string>
+#include "cubeb/cubeb.h"
+
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+const uint32_t SAMPLE_FREQUENCY = 48000;
+const cubeb_sample_format SAMPLE_FORMAT = CUBEB_SAMPLE_S16NE;
+
+enum test_direction {
+ INPUT_ONLY,
+ OUTPUT_ONLY,
+ DUPLEX
+};
+
+// Structure which is used by data callbacks to track the total callbacks
+// executed vs the number of callbacks expected.
+struct user_state_callback_ret {
+ std::atomic<int> cb_count{ 0 };
+ std::atomic<int> expected_cb_count{ 0 };
+ std::atomic<int> error_state{ 0 };
+};
+
+// Data callback that always returns 0
+long data_cb_ret_zero(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ user_state_callback_ret * u = (user_state_callback_ret *) user;
+ // If this is the first time the callback has been called set our expected
+ // callback count
+ if (u->cb_count == 0) {
+ u->expected_cb_count = 1;
+ }
+ u->cb_count++;
+ if (nframes < 1) {
+ // This shouldn't happen
+ EXPECT_TRUE(false) << "nframes should not be 0 in data callback!";
+ }
+ return 0;
+}
+
+// Data callback that always returns nframes - 1
+long data_cb_ret_nframes_minus_one(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ user_state_callback_ret * u = (user_state_callback_ret *)user;
+ // If this is the first time the callback has been called set our expected
+ // callback count
+ if (u->cb_count == 0) {
+ u->expected_cb_count = 1;
+ }
+ u->cb_count++;
+ if (nframes < 1) {
+ // This shouldn't happen
+ EXPECT_TRUE(false) << "nframes should not be 0 in data callback!";
+ }
+ if (outputbuffer != NULL) {
+ // If we have an output buffer insert silence
+ short * ob = (short *) outputbuffer;
+ for (long i = 0; i < nframes - 1; i++) {
+ ob[i] = 0;
+ }
+ }
+ return nframes - 1;
+}
+
+// Data callback that always returns nframes
+long data_cb_ret_nframes(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ user_state_callback_ret * u = (user_state_callback_ret *)user;
+ u->cb_count++;
+ // Every callback returns nframes, so every callback is expected
+ u->expected_cb_count++;
+ if (nframes < 1) {
+ // This shouldn't happen
+ EXPECT_TRUE(false) << "nframes should not be 0 in data callback!";
+ }
+ if (outputbuffer != NULL) {
+ // If we have an output buffer insert silence
+ short * ob = (short *) outputbuffer;
+ for (long i = 0; i < nframes; i++) {
+ ob[i] = 0;
+ }
+ }
+ return nframes;
+}
+
+// Data callback that always returns CUBEB_ERROR
+long
+data_cb_ret_error(cubeb_stream * stream, void * user, const void * inputbuffer,
+ void * outputbuffer, long nframes)
+{
+ user_state_callback_ret * u = (user_state_callback_ret *)user;
+ // If this is the first time the callback has been called set our expected
+ // callback count
+ if (u->cb_count == 0) {
+ u->expected_cb_count = 1;
+ }
+ u->cb_count++;
+ if (nframes < 1) {
+ // This shouldn't happen
+ EXPECT_TRUE(false) << "nframes should not be 0 in data callback!";
+ }
+ return CUBEB_ERROR;
+}
+
+void state_cb_ret(cubeb_stream * stream, void * user, cubeb_state state)
+{
+ if (stream == NULL)
+ return;
+ user_state_callback_ret * u = (user_state_callback_ret *)user;
+
+ switch (state) {
+ case CUBEB_STATE_STARTED:
+ fprintf(stderr, "stream started\n"); break;
+ case CUBEB_STATE_STOPPED:
+ fprintf(stderr, "stream stopped\n"); break;
+ case CUBEB_STATE_DRAINED:
+ fprintf(stderr, "stream drained\n"); break;
+ case CUBEB_STATE_ERROR:
+ fprintf(stderr, "stream error\n");
+ u->error_state.fetch_add(1);
+ break;
+ default:
+ fprintf(stderr, "unknown stream state %d\n", state);
+ }
+}
+
+void run_test_callback(test_direction direction,
+ cubeb_data_callback data_cb,
+ const std::string & test_desc) {
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r;
+ user_state_callback_ret user_state;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb callback return value example");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ if ((direction == INPUT_ONLY || direction == DUPLEX) &&
+ !can_run_audio_input_test(ctx)) {
+ /* This test needs an available input device, skip it if this host does not
+ * have one or if the backend doesn't implement input. */
+ return;
+ }
+
+ // Setup all params, but only pass them later as required by direction
+ input_params.format = SAMPLE_FORMAT;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = 1;
+ input_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = CUBEB_STREAM_PREF_NONE;
+ output_params = input_params;
+
+ r = cubeb_get_min_latency(ctx, &input_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ switch (direction)
+ {
+ case INPUT_ONLY:
+ r = cubeb_stream_init(ctx, &stream, "Cubeb callback ret input",
+ NULL, &input_params, NULL, NULL,
+ latency_frames, data_cb, state_cb_ret, &user_state);
+ break;
+ case OUTPUT_ONLY:
+ r = cubeb_stream_init(ctx, &stream, "Cubeb callback ret output",
+ NULL, NULL, NULL, &output_params,
+ latency_frames, data_cb, state_cb_ret, &user_state);
+ break;
+ case DUPLEX:
+ r = cubeb_stream_init(ctx, &stream, "Cubeb callback ret duplex",
+ NULL, &input_params, NULL, &output_params,
+ latency_frames, data_cb, state_cb_ret, &user_state);
+ break;
+ default:
+ ASSERT_TRUE(false) << "Unrecognized test direction!";
+ }
+ EXPECT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(stream);
+ delay(100);
+ cubeb_stream_stop(stream);
+
+ ASSERT_EQ(user_state.expected_cb_count, user_state.cb_count) <<
+ "Callback called unexpected number of times for " << test_desc << "!";
+ // TODO: On some test configurations, the data_callback is never called.
+ if (data_cb == data_cb_ret_error && user_state.cb_count != 0) {
+ ASSERT_EQ(user_state.error_state, 1) << "Callback expected error state";
+ }
+}
+
+TEST(cubeb, test_input_callback)
+{
+ run_test_callback(INPUT_ONLY, data_cb_ret_zero, "input only, return 0");
+ run_test_callback(INPUT_ONLY, data_cb_ret_nframes_minus_one, "input only, return nframes - 1");
+ run_test_callback(INPUT_ONLY, data_cb_ret_nframes, "input only, return nframes");
+ run_test_callback(INPUT_ONLY, data_cb_ret_error,
+ "input only, return CUBEB_ERROR");
+}
+
+TEST(cubeb, test_output_callback)
+{
+ run_test_callback(OUTPUT_ONLY, data_cb_ret_zero, "output only, return 0");
+ run_test_callback(OUTPUT_ONLY, data_cb_ret_nframes_minus_one, "output only, return nframes - 1");
+ run_test_callback(OUTPUT_ONLY, data_cb_ret_nframes, "output only, return nframes");
+ run_test_callback(OUTPUT_ONLY, data_cb_ret_error,
+ "output only, return CUBEB_ERROR");
+}
+
+TEST(cubeb, test_duplex_callback)
+{
+ run_test_callback(DUPLEX, data_cb_ret_zero, "duplex, return 0");
+ run_test_callback(DUPLEX, data_cb_ret_nframes_minus_one, "duplex, return nframes - 1");
+ run_test_callback(DUPLEX, data_cb_ret_nframes, "duplex, return nframes");
+ run_test_callback(DUPLEX, data_cb_ret_error, "duplex, return CUBEB_ERROR");
+}
diff --git a/media/libcubeb/test/test_deadlock.cpp b/media/libcubeb/test/test_deadlock.cpp
new file mode 100644
index 0000000000..373ba6ad7e
--- /dev/null
+++ b/media/libcubeb/test/test_deadlock.cpp
@@ -0,0 +1,262 @@
+/*
+ * Copyright © 2017 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ *
+ *
+ * Purpose
+ * =============================================================================
+ * In CoreAudio, the data callback will holds a mutex shared with AudioUnit
+ * (mutex_AU). Thus, if the callback request another mutex M held by the another
+ * function, without releasing mutex_AU, then it will cause a deadlock when the
+ * another function, which holds the mutex M, request to use AudioUnit.
+ *
+ * The following figure illustrates the deadlock in bug 1337805:
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1337805
+ * (The detail analysis can be found on bug 1350511:
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1350511)
+ *
+ * holds
+ * data_callback <---------- mutext_AudioUnit(mutex_AU)
+ * | ^
+ * | |
+ * | request | request
+ * | |
+ * v holds |
+ * mutex_cubeb ------------> get_channel_layout
+ *
+ * In this example, the "audiounit_get_channel_layout" in f4edfb8:
+ * https://github.com/kinetiknz/cubeb/blob/f4edfb8eea920887713325e44773f3a2d959860c/src/cubeb_audiounit.cpp#L2725
+ * requests the mutex_AU to create an AudioUnit, when it holds a mutex for cubeb
+ * context. Meanwhile, the data callback who holds the mutex_AU requests the
+ * mutex for cubeb context. As a result, it causes a deadlock.
+ *
+ * The problem is solve by pull 236: https://github.com/kinetiknz/cubeb/pull/236
+ * We store the latest channel layout and return it when there is an active
+ * AudioUnit, otherwise, we will create an AudioUnit to get it.
+ *
+ * Although the problem is solved, to prevent it happens again, we add the test
+ * here in case someone without such knowledge misuses the AudioUnit in
+ * get_channel_layout. Moreover, it's a good way to record the known issues
+ * to warn other developers.
+ */
+
+#include "gtest/gtest.h"
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
+#include "common.h" // for layout_infos
+#include "cubeb/cubeb.h" // for cubeb utils
+#include "cubeb_utils.h" // for owned_critical_section, auto_lock
+#include <iostream> // for fprintf
+#include <pthread.h> // for pthread
+#include <signal.h> // for signal
+#include <stdexcept> // for std::logic_error
+#include <string> // for std::string
+#include <unistd.h> // for sleep, usleep
+#include <atomic> // for std::atomic
+
+// The signal alias for calling our thread killer.
+#define CALL_THREAD_KILLER SIGUSR1
+
+// This indicator will become true when our pending task thread is killed by
+// ourselves.
+bool killed = false;
+
+// This indicator will become true when the assigned task is done.
+std::atomic<bool> task_done{ false };
+
+// Indicating the data callback is fired or not.
+bool called = false;
+
+// Toggle to true when running data callback. Before data callback gets
+// the mutex for cubeb context, it toggles back to false.
+// The task to get channel layout should be executed when this is true.
+std::atomic<bool> callbacking_before_getting_context{ false };
+
+owned_critical_section context_mutex;
+cubeb * context = nullptr;
+
+cubeb * get_cubeb_context_unlocked()
+{
+ if (context) {
+ return context;
+ }
+
+ int r = CUBEB_OK;
+ r = common_init(&context, "Cubeb deadlock test");
+ if (r != CUBEB_OK) {
+ context = nullptr;
+ }
+
+ return context;
+}
+
+cubeb * get_cubeb_context()
+{
+ auto_lock lock(context_mutex);
+ return get_cubeb_context_unlocked();
+}
+
+void state_cb_audio(cubeb_stream * /*stream*/, void * /*user*/, cubeb_state /*state*/)
+{
+}
+
+// Fired by coreaudio's rendering mechanism. It holds a mutex shared with the
+// current used AudioUnit.
+template<typename T>
+long data_cb(cubeb_stream * /*stream*/, void * /*user*/,
+ const void * /*inputbuffer*/, void * outputbuffer, long nframes)
+{
+ called = true;
+
+ uint64_t tid; // Current thread id.
+ pthread_threadid_np(NULL, &tid);
+ fprintf(stderr, "Audio output is on thread %llu\n", tid);
+
+ if (!task_done) {
+ callbacking_before_getting_context = true;
+ fprintf(stderr, "[%llu] time to switch thread\n", tid);
+ // Force to switch threads by sleeping 10 ms. Notice that anything over
+ // 10ms would create a glitch. It's intended here for test, so the delay
+ // is ok.
+ usleep(10000);
+ callbacking_before_getting_context = false;
+ }
+
+ fprintf(stderr, "[%llu] try getting backend id ...\n", tid);
+
+ // Try requesting mutex for context by get_cubeb_context()
+ // when holding a mutex for AudioUnit.
+ char const * backend_id = cubeb_get_backend_id(get_cubeb_context());
+ fprintf(stderr, "[%llu] callback on %s\n", tid, backend_id);
+
+ // Mute the output (or get deaf)
+ memset(outputbuffer, 0, nframes * 2 * sizeof(float));
+ return nframes;
+}
+
+// Called by wait_to_get_layout, which is run out of main thread.
+void get_preferred_channel_layout()
+{
+ auto_lock lock(context_mutex);
+ cubeb * context = get_cubeb_context_unlocked();
+ ASSERT_TRUE(!!context);
+
+ // We will cause a deadlock if cubeb_get_preferred_channel_layout requests
+ // mutex for AudioUnit when it holds mutex for context.
+ cubeb_channel_layout layout;
+ int r = cubeb_get_preferred_channel_layout(context, &layout);
+ ASSERT_EQ(r == CUBEB_OK, layout != CUBEB_LAYOUT_UNDEFINED);
+ fprintf(stderr, "layout is %s\n", layout_infos[layout].name);
+}
+
+void * wait_to_get_layout(void *)
+{
+ uint64_t tid; // Current thread id.
+ pthread_threadid_np(NULL, &tid);
+
+ while(!callbacking_before_getting_context) {
+ fprintf(stderr, "[%llu] waiting for data callback ...\n", tid);
+ usleep(1000); // Force to switch threads by sleeping 1 ms.
+ }
+
+ fprintf(stderr, "[%llu] try getting channel layout ...\n", tid);
+ get_preferred_channel_layout(); // Deadlock checkpoint.
+ task_done = true;
+
+ return NULL;
+}
+
+void * watchdog(void * s)
+{
+ uint64_t tid; // Current thread id.
+ pthread_threadid_np(NULL, &tid);
+
+ pthread_t subject = *((pthread_t *) s);
+ uint64_t stid; // task thread id.
+ pthread_threadid_np(subject, &stid);
+
+ unsigned int sec = 2;
+ fprintf(stderr, "[%llu] sleep %d seconds before checking task for thread %llu\n", tid, sec, stid);
+ sleep(sec); // Force to switch threads.
+
+ fprintf(stderr, "[%llu] check task for thread %llu now\n", tid, stid);
+ if (!task_done) {
+ fprintf(stderr, "[%llu] kill the task thread %llu\n", tid, stid);
+ pthread_kill(subject, CALL_THREAD_KILLER);
+ pthread_detach(subject);
+ // pthread_kill doesn't release the mutex held by the killed thread,
+ // so we need to unlock it manually.
+ context_mutex.unlock();
+ }
+ fprintf(stderr, "[%llu] the assigned task for thread %llu is %sdone\n", tid, stid, (task_done) ? "" : "not ");
+
+ return NULL;
+}
+
+void thread_killer(int signal)
+{
+ ASSERT_EQ(signal, CALL_THREAD_KILLER);
+ fprintf(stderr, "task thread is killed!\n");
+ killed = true;
+}
+
+TEST(cubeb, run_deadlock_test)
+{
+#if !defined(__APPLE__)
+ FAIL() << "Deadlock test is only for OSX now";
+#endif
+
+ cubeb * ctx = get_cubeb_context();
+ ASSERT_TRUE(!!ctx);
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ cubeb_stream_params params;
+ params.format = CUBEB_SAMPLE_FLOAT32NE;
+ params.rate = 44100;
+ params.channels = 2;
+ params.layout = CUBEB_LAYOUT_STEREO;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ cubeb_stream * stream = NULL;
+ int r = cubeb_stream_init(ctx, &stream, "test deadlock", NULL, NULL, NULL,
+ &params, 512, &data_cb<float>, state_cb_audio, NULL);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ // Install signal handler.
+ signal(CALL_THREAD_KILLER, thread_killer);
+
+ pthread_t subject, detector;
+ pthread_create(&subject, NULL, wait_to_get_layout, NULL);
+ pthread_create(&detector, NULL, watchdog, (void *) &subject);
+
+ uint64_t stid, dtid;
+ pthread_threadid_np(subject, &stid);
+ pthread_threadid_np(detector, &dtid);
+ fprintf(stderr, "task thread %llu, monitor thread %llu are created\n", stid, dtid);
+
+ cubeb_stream_start(stream);
+
+ pthread_join(subject, NULL);
+ pthread_join(detector, NULL);
+
+ ASSERT_TRUE(called);
+
+ fprintf(stderr, "\n%sDeadlock detected!\n", (called && !task_done.load()) ? "" : "No ");
+
+ // Check the task is killed by ourselves if deadlock happends.
+ // Otherwise, thread_killer should not be triggered.
+ ASSERT_NE(task_done.load(), killed);
+
+ ASSERT_TRUE(task_done.load());
+
+ cubeb_stream_stop(stream);
+}
+
+#undef CALL_THREAD_KILLER
diff --git a/media/libcubeb/test/test_device_changed_callback.cpp b/media/libcubeb/test/test_device_changed_callback.cpp
new file mode 100644
index 0000000000..890ebd9e41
--- /dev/null
+++ b/media/libcubeb/test/test_device_changed_callback.cpp
@@ -0,0 +1,121 @@
+/*
+ * Copyright © 2018 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+/* libcubeb api/function test. Check behaviors of registering device changed
+ * callbacks for the streams. */
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include <stdio.h>
+#include <memory>
+#include "cubeb/cubeb.h"
+
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+#define SAMPLE_FREQUENCY 48000
+#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
+#define INPUT_CHANNELS 1
+#define INPUT_LAYOUT CUBEB_LAYOUT_MONO
+#define OUTPUT_CHANNELS 2
+#define OUTPUT_LAYOUT CUBEB_LAYOUT_STEREO
+
+long data_callback(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ return 0;
+}
+
+void state_callback(cubeb_stream * stream, void * user, cubeb_state state)
+{
+}
+
+void device_changed_callback(void * user)
+{
+ fprintf(stderr, "device changed callback\n");
+ ASSERT_TRUE(false) << "Error: device changed callback"
+ " called without changing devices";
+}
+
+void test_registering_null_callback_twice(cubeb_stream * stream)
+{
+ int r = cubeb_stream_register_device_changed_callback(stream, nullptr);
+ if (r == CUBEB_ERROR_NOT_SUPPORTED) {
+ return;
+ }
+ ASSERT_EQ(r, CUBEB_OK) << "Error registering null device changed callback";
+
+ r = cubeb_stream_register_device_changed_callback(stream, nullptr);
+ ASSERT_EQ(r, CUBEB_OK) << "Error registering null device changed callback again";
+}
+
+void test_registering_and_unregistering_callback(cubeb_stream * stream)
+{
+ int r = cubeb_stream_register_device_changed_callback(stream, device_changed_callback);
+ if (r == CUBEB_ERROR_NOT_SUPPORTED) {
+ return;
+ }
+ ASSERT_EQ(r, CUBEB_OK) << "Error registering device changed callback";
+
+ r = cubeb_stream_register_device_changed_callback(stream, nullptr);
+ ASSERT_EQ(r, CUBEB_OK) << "Error unregistering device changed callback";
+}
+
+TEST(cubeb, device_changed_callbacks)
+{
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r = CUBEB_OK;
+ uint32_t latency_frames = 0;
+
+
+ r = common_init(&ctx, "Cubeb duplex example with device change");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ if (!can_run_audio_input_test(ctx)) {
+ return;
+ }
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ /* typical user-case: mono input, stereo output, low latency. */
+ input_params.format = STREAM_FORMAT;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = INPUT_CHANNELS;
+ input_params.layout = INPUT_LAYOUT;
+ input_params.prefs = CUBEB_STREAM_PREF_NONE;
+ output_params.format = STREAM_FORMAT;
+ output_params.rate = SAMPLE_FREQUENCY;
+ output_params.channels = OUTPUT_CHANNELS;
+ output_params.layout = OUTPUT_LAYOUT;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ r = cubeb_stream_init(ctx, &stream, "Cubeb duplex",
+ NULL, &input_params, NULL, &output_params,
+ latency_frames, data_callback, state_callback, nullptr);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ test_registering_null_callback_twice(stream);
+
+ test_registering_and_unregistering_callback(stream);
+
+ cubeb_stream_destroy(stream);
+}
+
+#undef SAMPLE_FREQUENCY
+#undef STREAM_FORMAT
+#undef INPUT_CHANNELS
+#undef INPUT_LAYOUT
+#undef OUTPUT_CHANNELS
+#undef OUTPUT_LAYOUT
diff --git a/media/libcubeb/test/test_devices.cpp b/media/libcubeb/test/test_devices.cpp
new file mode 100644
index 0000000000..76e96159ee
--- /dev/null
+++ b/media/libcubeb/test/test_devices.cpp
@@ -0,0 +1,262 @@
+/*
+ * 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. */
+#include "gtest/gtest.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <memory>
+#include "cubeb/cubeb.h"
+
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+long data_cb_duplex(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ // noop, unused
+ return 0;
+}
+
+void state_cb_duplex(cubeb_stream * stream, void * /*user*/, cubeb_state state)
+{
+ // noop, unused
+}
+
+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 %u frames, hi %u frames\n",
+ info->device_id, info->preferred ? " (PREFERRED)" : "",
+ info->friendly_name, info->group_id, info->vendor_name,
+ devtype, devstate, info->max_channels,
+ (devfmts[0] == '\0') ? devfmts : devfmts + 1,
+ (unsigned int)info->format, devdeffmt,
+ info->min_rate, info->max_rate, info->default_rate,
+ info->latency_lo, info->latency_hi);
+}
+
+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);
+}
+
+TEST(cubeb, destroy_default_collection)
+{
+ int r;
+ cubeb * ctx = NULL;
+ cubeb_device_collection collection{ nullptr, 0 };
+
+ r = common_init(&ctx, "Cubeb audio test");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ ASSERT_EQ(collection.device, nullptr);
+ ASSERT_EQ(collection.count, (size_t) 0);
+
+ r = cubeb_device_collection_destroy(ctx, &collection);
+ if (r != CUBEB_ERROR_NOT_SUPPORTED) {
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_EQ(collection.device, nullptr);
+ ASSERT_EQ(collection.count, (size_t) 0);
+ }
+}
+
+TEST(cubeb, enumerate_devices)
+{
+ int r;
+ cubeb * ctx = NULL;
+ cubeb_device_collection collection;
+
+ r = common_init(&ctx, "Cubeb audio test");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ 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_ERROR_NOT_SUPPORTED) {
+ fprintf(stderr, "Device enumeration not supported"
+ " for this backend, skipping this test.\n");
+ return;
+ }
+ ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;
+
+ fprintf(stdout, "Found %zu input devices\n", collection.count);
+ print_device_collection(&collection, stdout);
+ cubeb_device_collection_destroy(ctx, &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);
+ ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;
+
+ fprintf(stdout, "Found %zu output devices\n", collection.count);
+ print_device_collection(&collection, stdout);
+ cubeb_device_collection_destroy(ctx, &collection);
+
+ uint32_t count_before_creating_duplex_stream;
+ r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection);
+ ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;
+ count_before_creating_duplex_stream = collection.count;
+ cubeb_device_collection_destroy(ctx, &collection);
+
+ if (!can_run_audio_input_test(ctx)) {
+ return;
+ }
+ cubeb_stream * stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+
+ input_params.format = output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ input_params.rate = output_params.rate = 48000;
+ input_params.channels = output_params.channels = 1;
+ input_params.layout = output_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_stream_init(ctx, &stream, "Cubeb duplex",
+ NULL, &input_params, NULL, &output_params,
+ 1024, data_cb_duplex, state_cb_duplex, nullptr);
+
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection);
+ ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;
+ ASSERT_EQ(count_before_creating_duplex_stream, collection.count);
+ cubeb_device_collection_destroy(ctx, &collection);
+
+ cubeb_stream_destroy(stream);
+}
+
+TEST(cubeb, stream_get_current_device)
+{
+ cubeb * ctx = NULL;
+ int r = common_init(&ctx, "Cubeb audio test");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ fprintf(stdout, "Getting current devices for backend %s\n",
+ cubeb_get_backend_id(ctx));
+
+ if (!can_run_audio_input_test(ctx)) {
+ return;
+ }
+
+ cubeb_stream * stream = NULL;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+
+ input_params.format = output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ input_params.rate = output_params.rate = 48000;
+ input_params.channels = output_params.channels = 1;
+ input_params.layout = output_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_stream_init(ctx, &stream, "Cubeb duplex",
+ NULL, &input_params, NULL, &output_params,
+ 1024, data_cb_duplex, state_cb_duplex, nullptr);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ cubeb_device * device;
+ r = cubeb_stream_get_current_device(stream, &device);
+ if (r == CUBEB_ERROR_NOT_SUPPORTED) {
+ fprintf(stderr, "Getting current device is not supported"
+ " for this backend, skipping this test.\n");
+ return;
+ }
+ ASSERT_EQ(r, CUBEB_OK) << "Error getting current devices";
+
+ fprintf(stdout, "Current output device: %s\n", device->output_name);
+ fprintf(stdout, "Current input device: %s\n", device->input_name);
+
+ r = cubeb_stream_device_destroy(stream, device);
+ ASSERT_EQ(r, CUBEB_OK) << "Error destroying current devices";
+}
diff --git a/media/libcubeb/test/test_duplex.cpp b/media/libcubeb/test/test_duplex.cpp
new file mode 100644
index 0000000000..1eb661e0e6
--- /dev/null
+++ b/media/libcubeb/test/test_duplex.cpp
@@ -0,0 +1,360 @@
+/*
+ * 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. */
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <memory>
+#include "cubeb/cubeb.h"
+#include <atomic>
+
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+#define SAMPLE_FREQUENCY 48000
+#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
+#define INPUT_CHANNELS 1
+#define INPUT_LAYOUT CUBEB_LAYOUT_MONO
+#define OUTPUT_CHANNELS 2
+#define OUTPUT_LAYOUT CUBEB_LAYOUT_STEREO
+
+struct user_state_duplex
+{
+ std::atomic<int> invalid_audio_value{ 0 };
+};
+
+long data_cb_duplex(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ user_state_duplex * u = reinterpret_cast<user_state_duplex*>(user);
+ float *ib = (float *)inputbuffer;
+ float *ob = (float *)outputbuffer;
+
+ 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] <= -1.0 || ib[i] >= 1.0) {
+ u->invalid_audio_value = 1;
+ break;
+ }
+ ob[output_index] = ob[output_index + 1] = ib[i];
+ output_index += 2;
+ }
+
+ return nframes;
+}
+
+void state_cb_duplex(cubeb_stream * stream, void * /*user*/, cubeb_state state)
+{
+ if (stream == NULL)
+ return;
+
+ switch (state) {
+ case CUBEB_STATE_STARTED:
+ fprintf(stderr, "stream started\n"); break;
+ case CUBEB_STATE_STOPPED:
+ fprintf(stderr, "stream stopped\n"); break;
+ case CUBEB_STATE_DRAINED:
+ fprintf(stderr, "stream drained\n"); break;
+ default:
+ fprintf(stderr, "unknown stream state %d\n", state);
+ }
+
+ return;
+}
+
+TEST(cubeb, duplex)
+{
+ cubeb *ctx;
+ cubeb_stream *stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r;
+ user_state_duplex stream_state;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb duplex example");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ /* This test needs an available input device, skip it if this host does not
+ * have one. */
+ if (!can_run_audio_input_test(ctx)) {
+ return;
+ }
+
+ /* typical user-case: mono input, stereo output, low latency. */
+ input_params.format = STREAM_FORMAT;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = INPUT_CHANNELS;
+ input_params.layout = INPUT_LAYOUT;
+ input_params.prefs = CUBEB_STREAM_PREF_NONE;
+ output_params.format = STREAM_FORMAT;
+ output_params.rate = SAMPLE_FREQUENCY;
+ output_params.channels = OUTPUT_CHANNELS;
+ output_params.layout = OUTPUT_LAYOUT;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ r = cubeb_stream_init(ctx, &stream, "Cubeb duplex",
+ NULL, &input_params, NULL, &output_params,
+ latency_frames, data_cb_duplex, state_cb_duplex, &stream_state);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(stream);
+ delay(500);
+ cubeb_stream_stop(stream);
+
+ ASSERT_FALSE(stream_state.invalid_audio_value.load());
+}
+
+void device_collection_changed_callback(cubeb * context, void * user)
+{
+ fprintf(stderr, "collection changed callback\n");
+ ASSERT_TRUE(false) << "Error: device collection changed callback"
+ " called when opening a stream";
+}
+
+void
+duplex_collection_change_impl(cubeb * ctx)
+{
+ cubeb_stream * stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r;
+ uint32_t latency_frames = 0;
+
+ r = cubeb_register_device_collection_changed(
+ ctx, static_cast<cubeb_device_type>(CUBEB_DEVICE_TYPE_INPUT),
+ device_collection_changed_callback, nullptr);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+
+ /* typical user-case: mono input, stereo output, low latency. */
+ input_params.format = STREAM_FORMAT;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = INPUT_CHANNELS;
+ input_params.layout = INPUT_LAYOUT;
+ input_params.prefs = CUBEB_STREAM_PREF_NONE;
+ output_params.format = STREAM_FORMAT;
+ output_params.rate = SAMPLE_FREQUENCY;
+ output_params.channels = OUTPUT_CHANNELS;
+ output_params.layout = OUTPUT_LAYOUT;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ r = cubeb_stream_init(ctx, &stream, "Cubeb duplex", NULL, &input_params, NULL,
+ &output_params, latency_frames, data_cb_duplex,
+ state_cb_duplex, nullptr);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+ cubeb_stream_destroy(stream);
+}
+
+TEST(cubeb, duplex_collection_change)
+{
+ cubeb * ctx;
+ int r;
+
+ r = common_init(&ctx, "Cubeb duplex example with collection change");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit(
+ ctx, cubeb_destroy);
+
+ /* This test needs an available input device, skip it if this host does not
+ * have one. */
+ if (!can_run_audio_input_test(ctx)) {
+ return;
+ }
+
+ duplex_collection_change_impl(ctx);
+ r = cubeb_register_device_collection_changed(
+ ctx, static_cast<cubeb_device_type>(CUBEB_DEVICE_TYPE_INPUT), nullptr,
+ nullptr);
+ ASSERT_EQ(r, CUBEB_OK);
+}
+
+TEST(cubeb, duplex_collection_change_no_unregister)
+{
+ cubeb * ctx;
+ int r;
+
+ r = common_init(&ctx, "Cubeb duplex example with collection change");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ /* This test needs an available input device, skip it if this host does not
+ * have one. */
+ if (!can_run_audio_input_test(ctx)) {
+ cubeb_destroy(ctx);
+ return;
+ }
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit(
+ ctx, [](cubeb * p) noexcept { EXPECT_DEATH(cubeb_destroy(p), ""); });
+
+ duplex_collection_change_impl(ctx);
+}
+
+long data_cb_input(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ if (stream == NULL || inputbuffer == NULL || outputbuffer != NULL) {
+ return CUBEB_ERROR;
+ }
+
+ return nframes;
+}
+
+void state_cb_input(cubeb_stream * stream, void * /*user*/, cubeb_state state)
+{
+ if (stream == NULL)
+ return;
+
+ switch (state) {
+ case CUBEB_STATE_STARTED:
+ fprintf(stderr, "stream started\n"); break;
+ case CUBEB_STATE_STOPPED:
+ fprintf(stderr, "stream stopped\n"); break;
+ case CUBEB_STATE_DRAINED:
+ fprintf(stderr, "stream drained\n"); break;
+ case CUBEB_STATE_ERROR:
+ fprintf(stderr, "stream runs into error state\n"); break;
+ default:
+ fprintf(stderr, "unknown stream state %d\n", state);
+ }
+
+ return;
+}
+
+std::vector<cubeb_devid> get_devices(cubeb * ctx, cubeb_device_type type) {
+ std::vector<cubeb_devid> devices;
+
+ cubeb_device_collection collection;
+ int r = cubeb_enumerate_devices(ctx, type, &collection);
+
+ if (r != CUBEB_OK) {
+ fprintf(stderr, "Failed to enumerate devices\n");
+ return devices;
+ }
+
+ for (uint32_t i = 0; i < collection.count; i++) {
+ if (collection.device[i].state == CUBEB_DEVICE_STATE_ENABLED) {
+ devices.emplace_back(collection.device[i].devid);
+ }
+ }
+
+ cubeb_device_collection_destroy(ctx, &collection);
+
+ return devices;
+}
+
+TEST(cubeb, one_duplex_one_input)
+{
+ cubeb *ctx;
+ cubeb_stream *duplex_stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r;
+ user_state_duplex duplex_stream_state;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb duplex example");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ /* This test needs at least two available input devices. */
+ std::vector<cubeb_devid> input_devices = get_devices(ctx, CUBEB_DEVICE_TYPE_INPUT);
+ if (input_devices.size() < 2) {
+ return;
+ }
+
+ /* This test needs at least one available output device. */
+ std::vector<cubeb_devid> output_devices = get_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT);
+ if (output_devices.size() < 1) {
+ return;
+ }
+
+ cubeb_devid duplex_input = input_devices.front();
+ cubeb_devid duplex_output = nullptr; // default device
+ cubeb_devid input_only = input_devices.back();
+
+ /* typical use-case: mono voice input, stereo output, low latency. */
+ input_params.format = STREAM_FORMAT;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = INPUT_CHANNELS;
+ input_params.layout = CUBEB_LAYOUT_UNDEFINED;
+ input_params.prefs = CUBEB_STREAM_PREF_VOICE;
+
+ output_params.format = STREAM_FORMAT;
+ output_params.rate = SAMPLE_FREQUENCY;
+ output_params.channels = OUTPUT_CHANNELS;
+ output_params.layout = OUTPUT_LAYOUT;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ r = cubeb_stream_init(ctx, &duplex_stream, "Cubeb duplex",
+ duplex_input, &input_params, duplex_output, &output_params,
+ latency_frames, data_cb_duplex, state_cb_duplex, &duplex_stream_state);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing duplex cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(duplex_stream, cubeb_stream_destroy);
+
+ r = cubeb_stream_start(duplex_stream);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not start duplex stream";
+ delay(500);
+
+ cubeb_stream *input_stream;
+ r = cubeb_stream_init(ctx, &input_stream, "Cubeb input",
+ input_only, &input_params, NULL, NULL,
+ latency_frames, data_cb_input, state_cb_input, nullptr);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing input-only cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy);
+
+ r = cubeb_stream_start(input_stream);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not start input stream";
+ delay(500);
+
+ r = cubeb_stream_stop(duplex_stream);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not stop duplex stream";
+
+ r = cubeb_stream_stop(input_stream);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not stop input stream";
+
+ ASSERT_FALSE(duplex_stream_state.invalid_audio_value.load());
+}
+
+#undef SAMPLE_FREQUENCY
+#undef STREAM_FORMAT
+#undef INPUT_CHANNELS
+#undef INPUT_LAYOUT
+#undef OUTPUT_CHANNELS
+#undef OUTPUT_LAYOUT
diff --git a/media/libcubeb/test/test_latency.cpp b/media/libcubeb/test/test_latency.cpp
new file mode 100644
index 0000000000..522851044a
--- /dev/null
+++ b/media/libcubeb/test/test_latency.cpp
@@ -0,0 +1,47 @@
+#include "gtest/gtest.h"
+#include <stdlib.h>
+#include <memory>
+#include "cubeb/cubeb.h"
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+TEST(cubeb, latency)
+{
+ cubeb * ctx = NULL;
+ int r;
+ uint32_t max_channels;
+ uint32_t preferred_rate;
+ uint32_t latency_frames;
+
+ r = common_init(&ctx, "Cubeb audio test");
+ ASSERT_EQ(r, CUBEB_OK);
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ r = cubeb_get_max_channel_count(ctx, &max_channels);
+ ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
+ if (r == CUBEB_OK) {
+ ASSERT_GT(max_channels, 0u);
+ }
+
+ r = cubeb_get_preferred_sample_rate(ctx, &preferred_rate);
+ ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
+ if (r == CUBEB_OK) {
+ ASSERT_GT(preferred_rate, 0u);
+ }
+
+ cubeb_stream_params params = {
+ CUBEB_SAMPLE_FLOAT32NE,
+ preferred_rate,
+ max_channels,
+ CUBEB_LAYOUT_UNDEFINED,
+ CUBEB_STREAM_PREF_NONE
+ };
+ r = cubeb_get_min_latency(ctx, &params, &latency_frames);
+ ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
+ if (r == CUBEB_OK) {
+ ASSERT_GT(latency_frames, 0u);
+ }
+}
diff --git a/media/libcubeb/test/test_logging.cpp b/media/libcubeb/test/test_logging.cpp
new file mode 100644
index 0000000000..5d9691d353
--- /dev/null
+++ b/media/libcubeb/test/test_logging.cpp
@@ -0,0 +1,193 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+/* cubeb_logging test */
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include "cubeb/cubeb.h"
+#include "cubeb_log.h"
+#include <atomic>
+#include <math.h>
+#include <memory>
+#include <stdio.h>
+#include <stdlib.h>
+#include <thread>
+
+#include "common.h"
+
+#define PRINT_LOGS_TO_STDERR 0
+
+std::atomic<uint32_t> log_statements_received = {0};
+std::atomic<uint32_t> data_callback_call_count = {0};
+
+void
+test_logging_callback(char const * fmt, ...)
+{
+ log_statements_received++;
+#if PRINT_LOGS_TO_STDERR == 1
+ char buf[1024];
+ va_list argslist;
+ va_start(argslist, fmt);
+ vsnprintf(buf, 1024, fmt, argslist);
+ fprintf(stderr, "%s\n", buf);
+ va_end(argslist);
+#endif // PRINT_LOGS_TO_STDERR
+}
+
+long
+data_cb_load(cubeb_stream * stream, void * user, const void * inputbuffer,
+ void * outputbuffer, long nframes)
+{
+ data_callback_call_count++;
+ return nframes;
+}
+
+void
+state_cb(cubeb_stream * stream, void * /*user*/, cubeb_state state)
+{
+ if (stream == NULL)
+ return;
+
+ switch (state) {
+ case CUBEB_STATE_STARTED:
+ fprintf(stderr, "stream started\n");
+ break;
+ case CUBEB_STATE_STOPPED:
+ fprintf(stderr, "stream stopped\n");
+ break;
+ case CUBEB_STATE_DRAINED:
+ fprintf(stderr, "stream drained\n");
+ break;
+ default:
+ fprintf(stderr, "unknown stream state %d\n", state);
+ }
+
+ return;
+}
+
+// Waits for at least one audio callback to have occured.
+void
+wait_for_audio_callback()
+{
+ uint32_t audio_callback_index =
+ data_callback_call_count.load(std::memory_order_acquire);
+ while (audio_callback_index ==
+ data_callback_call_count.load(std::memory_order_acquire)) {
+ delay(100);
+ }
+}
+
+TEST(cubeb, logging)
+{
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params output_params;
+ int r;
+ uint32_t latency_frames = 0;
+
+ cubeb_set_log_callback(CUBEB_LOG_NORMAL, test_logging_callback);
+
+ r = common_init(&ctx, "Cubeb logging test");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit(
+ ctx, cubeb_destroy);
+
+ output_params.format = CUBEB_SAMPLE_FLOAT32LE;
+ output_params.rate = 48000;
+ output_params.channels = 2;
+ output_params.layout = CUBEB_LAYOUT_STEREO;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ r = cubeb_stream_init(ctx, &stream, "Cubeb logging", NULL, NULL, NULL,
+ &output_params, latency_frames, data_cb_load, state_cb,
+ NULL);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ ASSERT_NE(log_statements_received.load(std::memory_order_acquire), 0u);
+
+ cubeb_set_log_callback(CUBEB_LOG_DISABLED, nullptr);
+ log_statements_received.store(0, std::memory_order_release);
+
+ // This is synchronous and we'll receive log messages on all backends that we
+ // test
+ cubeb_stream_start(stream);
+
+ ASSERT_EQ(log_statements_received.load(std::memory_order_acquire), 0u);
+
+ cubeb_set_log_callback(CUBEB_LOG_VERBOSE, test_logging_callback);
+
+ wait_for_audio_callback();
+
+ ASSERT_NE(log_statements_received.load(std::memory_order_acquire), 0u);
+
+ bool log_callback_set = true;
+ uint32_t iterations = 100;
+ while (iterations--) {
+ wait_for_audio_callback();
+
+ if (!log_callback_set) {
+ ASSERT_EQ(log_statements_received.load(std::memory_order_acquire), 0u);
+ // Set a logging callback, start logging
+ cubeb_set_log_callback(CUBEB_LOG_VERBOSE, test_logging_callback);
+ log_callback_set = true;
+ } else {
+ // Disable the logging callback, stop logging.
+ ASSERT_NE(log_statements_received.load(std::memory_order_acquire), 0u);
+ cubeb_set_log_callback(CUBEB_LOG_DISABLED, nullptr);
+ log_statements_received.store(0, std::memory_order_release);
+ // Disabling logging should flush any log message -- wait a bit and check
+ // that this is true.
+ ASSERT_EQ(log_statements_received.load(std::memory_order_acquire), 0u);
+ log_callback_set = false;
+ }
+ }
+
+ cubeb_stream_stop(stream);
+}
+
+TEST(cubeb, logging_stress)
+{
+ cubeb_set_log_callback(CUBEB_LOG_NORMAL, test_logging_callback);
+
+ std::atomic<bool> thread_done = {false};
+
+ auto t = std::thread([&thread_done]() {
+ uint32_t count = 0;
+ do {
+ while (rand() % 10) {
+ ALOG("Log message #%d!", count++);
+ }
+ } while (count < 1e4);
+ thread_done.store(true);
+ });
+
+ bool enabled = true;
+ while (!thread_done.load()) {
+ if (enabled) {
+ cubeb_set_log_callback(CUBEB_LOG_DISABLED, nullptr);
+ enabled = false;
+ } else {
+ cubeb_set_log_callback(CUBEB_LOG_NORMAL, test_logging_callback);
+ enabled = true;
+ }
+ }
+
+ cubeb_set_log_callback(CUBEB_LOG_DISABLED, nullptr);
+
+ t.join();
+
+ ASSERT_TRUE(true);
+}
diff --git a/media/libcubeb/test/test_loopback.cpp b/media/libcubeb/test/test_loopback.cpp
new file mode 100644
index 0000000000..bbb9409e67
--- /dev/null
+++ b/media/libcubeb/test/test_loopback.cpp
@@ -0,0 +1,596 @@
+/*
+ * Copyright © 2017 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+ /* libcubeb api/function test. Requests a loopback device and checks that
+ output is being looped back to input. NOTE: Usage of output devices while
+ performing this test will cause flakey results! */
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <algorithm>
+#include <memory>
+#include <mutex>
+#include <string>
+#include "cubeb/cubeb.h"
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
+#include "common.h"
+const uint32_t SAMPLE_FREQUENCY = 48000;
+const uint32_t TONE_FREQUENCY = 440;
+const double OUTPUT_AMPLITUDE = 0.25;
+const int32_t NUM_FRAMES_TO_OUTPUT = SAMPLE_FREQUENCY / 20; /* play ~50ms of samples */
+
+template<typename T> T ConvertSampleToOutput(double input);
+template<> float ConvertSampleToOutput(double input) { return float(input); }
+template<> short ConvertSampleToOutput(double input) { return short(input * 32767.0f); }
+
+template<typename T> double ConvertSampleFromOutput(T sample);
+template<> double ConvertSampleFromOutput(float sample) { return double(sample); }
+template<> double ConvertSampleFromOutput(short sample) { return double(sample / 32767.0); }
+
+/* Simple cross correlation to help find phase shift. Not a performant impl */
+std::vector<double> cross_correlate(std::vector<double> & f,
+ std::vector<double> & g,
+ size_t signal_length)
+{
+ /* the length we sweep our window through to find the cross correlation */
+ size_t sweep_length = f.size() - signal_length + 1;
+ std::vector<double> correlation;
+ correlation.reserve(sweep_length);
+ for (size_t i = 0; i < sweep_length; i++) {
+ double accumulator = 0.0;
+ for (size_t j = 0; j < signal_length; j++) {
+ accumulator += f.at(j) * g.at(i + j);
+ }
+ correlation.push_back(accumulator);
+ }
+ return correlation;
+}
+
+/* best effort discovery of phase shift between output and (looped) input*/
+size_t find_phase(std::vector<double> & output_frames,
+ std::vector<double> & input_frames,
+ size_t signal_length)
+{
+ std::vector<double> correlation = cross_correlate(output_frames, input_frames, signal_length);
+ size_t phase = 0;
+ double max_correlation = correlation.at(0);
+ for (size_t i = 1; i < correlation.size(); i++) {
+ if (correlation.at(i) > max_correlation) {
+ max_correlation = correlation.at(i);
+ phase = i;
+ }
+ }
+ return phase;
+}
+
+std::vector<double> normalize_frames(std::vector<double> & frames) {
+ double max = abs(*std::max_element(frames.begin(), frames.end(),
+ [](double a, double b) { return abs(a) < abs(b); }));
+ std::vector<double> normalized_frames;
+ normalized_frames.reserve(frames.size());
+ for (const double frame : frames) {
+ normalized_frames.push_back(frame / max);
+ }
+ return normalized_frames;
+}
+
+/* heuristic comparison of aligned output and input signals, gets flaky if TONE_FREQUENCY is too high */
+void compare_signals(std::vector<double> & output_frames,
+ std::vector<double> & input_frames)
+{
+ ASSERT_EQ(output_frames.size(), input_frames.size()) << "#Output frames != #input frames";
+ size_t num_frames = output_frames.size();
+ std::vector<double> normalized_output_frames = normalize_frames(output_frames);
+ std::vector<double> normalized_input_frames = normalize_frames(input_frames);
+
+ /* calculate mean absolute errors */
+ /* mean absolute errors between output and input */
+ double io_mas = 0.0;
+ /* mean absolute errors between output and silence */
+ double output_silence_mas = 0.0;
+ /* mean absolute errors between input and silence */
+ double input_silence_mas = 0.0;
+ for (size_t i = 0; i < num_frames; i++) {
+ io_mas += abs(normalized_output_frames.at(i) - normalized_input_frames.at(i));
+ output_silence_mas += abs(normalized_output_frames.at(i));
+ input_silence_mas += abs(normalized_input_frames.at(i));
+ }
+ io_mas /= num_frames;
+ output_silence_mas /= num_frames;
+ input_silence_mas /= num_frames;
+
+ ASSERT_LT(io_mas, output_silence_mas)
+ << "Error between output and input should be less than output and silence!";
+ ASSERT_LT(io_mas, input_silence_mas)
+ << "Error between output and input should be less than output and silence!";
+
+ /* make sure extrema are in (roughly) correct location */
+ /* number of maxima + minama expected in the frames*/
+ const long NUM_EXTREMA = 2 * TONE_FREQUENCY * NUM_FRAMES_TO_OUTPUT / SAMPLE_FREQUENCY;
+ /* expected index of first maxima */
+ const long FIRST_MAXIMUM_INDEX = SAMPLE_FREQUENCY / TONE_FREQUENCY / 4;
+ /* Threshold we expect all maxima and minima to be above or below. Ideally
+ the extrema would be 1 or -1, but particularly at the start of loopback
+ the values seen can be significantly lower. */
+ const double THRESHOLD = 0.5;
+
+ for (size_t i = 0; i < NUM_EXTREMA; i++) {
+ bool is_maximum = i % 2 == 0;
+ /* expected offset to current extreme: i * stide between extrema */
+ size_t offset = i * SAMPLE_FREQUENCY / TONE_FREQUENCY / 2;
+ if (is_maximum) {
+ ASSERT_GT(normalized_output_frames.at(FIRST_MAXIMUM_INDEX + offset), THRESHOLD)
+ << "Output frames have unexpected missing maximum!";
+ ASSERT_GT(normalized_input_frames.at(FIRST_MAXIMUM_INDEX + offset), THRESHOLD)
+ << "Input frames have unexpected missing maximum!";
+ } else {
+ ASSERT_LT(normalized_output_frames.at(FIRST_MAXIMUM_INDEX + offset), -THRESHOLD)
+ << "Output frames have unexpected missing minimum!";
+ ASSERT_LT(normalized_input_frames.at(FIRST_MAXIMUM_INDEX + offset), -THRESHOLD)
+ << "Input frames have unexpected missing minimum!";
+ }
+ }
+}
+
+struct user_state_loopback {
+ std::mutex user_state_mutex;
+ long position = 0;
+ /* track output */
+ std::vector<double> output_frames;
+ /* track input */
+ std::vector<double> input_frames;
+};
+
+template<typename T>
+long data_cb_loop_duplex(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ struct user_state_loopback * u = (struct user_state_loopback *) user;
+ T * ib = (T *) inputbuffer;
+ T * ob = (T *) outputbuffer;
+
+ if (stream == NULL || inputbuffer == NULL || outputbuffer == NULL) {
+ return CUBEB_ERROR;
+ }
+
+ std::lock_guard<std::mutex> lock(u->user_state_mutex);
+ /* generate our test tone on the fly */
+ for (int i = 0; i < nframes; i++) {
+ double tone = 0.0;
+ if (u->position + i < NUM_FRAMES_TO_OUTPUT) {
+ /* generate sine wave */
+ tone = sin(2 * M_PI*(i + u->position) * TONE_FREQUENCY / SAMPLE_FREQUENCY);
+ tone *= OUTPUT_AMPLITUDE;
+ }
+ ob[i] = ConvertSampleToOutput<T>(tone);
+ u->output_frames.push_back(tone);
+ /* store any looped back output, may be silence */
+ u->input_frames.push_back(ConvertSampleFromOutput(ib[i]));
+ }
+
+ u->position += nframes;
+
+ return nframes;
+}
+
+template<typename T>
+long data_cb_loop_input_only(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ struct user_state_loopback * u = (struct user_state_loopback *) user;
+ T * ib = (T *) inputbuffer;
+
+ if (outputbuffer != NULL) {
+ // Can't assert as it needs to return, so expect to fail instead
+ EXPECT_EQ(outputbuffer, (void *) NULL) << "outputbuffer should be null in input only callback";
+ return CUBEB_ERROR;
+ }
+
+ if (stream == NULL || inputbuffer == NULL) {
+ return CUBEB_ERROR;
+ }
+
+ std::lock_guard<std::mutex> lock(u->user_state_mutex);
+ for (int i = 0; i < nframes; i++) {
+ u->input_frames.push_back(ConvertSampleFromOutput(ib[i]));
+ }
+
+ return nframes;
+}
+
+template<typename T>
+long data_cb_playback(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ struct user_state_loopback * u = (struct user_state_loopback *) user;
+ T * ob = (T *) outputbuffer;
+
+ if (stream == NULL || outputbuffer == NULL) {
+ return CUBEB_ERROR;
+ }
+
+ std::lock_guard<std::mutex> lock(u->user_state_mutex);
+ /* generate our test tone on the fly */
+ for (int i = 0; i < nframes; i++) {
+ double tone = 0.0;
+ if (u->position + i < NUM_FRAMES_TO_OUTPUT) {
+ /* generate sine wave */
+ tone = sin(2 * M_PI*(i + u->position) * TONE_FREQUENCY / SAMPLE_FREQUENCY);
+ tone *= OUTPUT_AMPLITUDE;
+ }
+ ob[i] = ConvertSampleToOutput<T>(tone);
+ u->output_frames.push_back(tone);
+ }
+
+ u->position += nframes;
+
+ return nframes;
+}
+
+void state_cb_loop(cubeb_stream * stream, void * /*user*/, cubeb_state state)
+{
+ if (stream == NULL)
+ return;
+
+ switch (state) {
+ case CUBEB_STATE_STARTED:
+ fprintf(stderr, "stream started\n"); break;
+ case CUBEB_STATE_STOPPED:
+ fprintf(stderr, "stream stopped\n"); break;
+ case CUBEB_STATE_DRAINED:
+ fprintf(stderr, "stream drained\n"); break;
+ default:
+ fprintf(stderr, "unknown stream state %d\n", state);
+ }
+
+ return;
+}
+
+void run_loopback_duplex_test(bool is_float)
+{
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb loopback example: duplex stream");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ /* This test needs an available input device, skip it if this host does not
+ * have one. */
+ if (!can_run_audio_input_test(ctx)) {
+ return;
+ }
+
+ input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = 1;
+ input_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK;
+ output_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ output_params.rate = SAMPLE_FREQUENCY;
+ output_params.channels = 1;
+ output_params.layout = CUBEB_LAYOUT_MONO;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ std::unique_ptr<user_state_loopback> user_data(new user_state_loopback());
+ ASSERT_TRUE(!!user_data) << "Error allocating user data";
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ /* setup a duplex stream with loopback */
+ r = cubeb_stream_init(ctx, &stream, "Cubeb loopback",
+ NULL, &input_params, NULL, &output_params, latency_frames,
+ is_float ? data_cb_loop_duplex<float> : data_cb_loop_duplex<short>,
+ state_cb_loop, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(stream);
+ delay(300);
+ cubeb_stream_stop(stream);
+
+ /* access after stop should not happen, but lock just in case and to appease sanitization tools */
+ std::lock_guard<std::mutex> lock(user_data->user_state_mutex);
+ std::vector<double> & output_frames = user_data->output_frames;
+ std::vector<double> & input_frames = user_data->input_frames;
+ ASSERT_EQ(output_frames.size(), input_frames.size())
+ << "#Output frames != #input frames";
+
+ size_t phase = find_phase(user_data->output_frames, user_data->input_frames, NUM_FRAMES_TO_OUTPUT);
+
+ /* extract vectors of just the relevant signal from output and input */
+ auto output_frames_signal_start = output_frames.begin();
+ auto output_frames_signal_end = output_frames.begin() + NUM_FRAMES_TO_OUTPUT;
+ std::vector<double> trimmed_output_frames(output_frames_signal_start, output_frames_signal_end);
+ auto input_frames_signal_start = input_frames.begin() + phase;
+ auto input_frames_signal_end = input_frames.begin() + phase + NUM_FRAMES_TO_OUTPUT;
+ std::vector<double> trimmed_input_frames(input_frames_signal_start, input_frames_signal_end);
+
+ compare_signals(trimmed_output_frames, trimmed_input_frames);
+}
+
+TEST(cubeb, loopback_duplex)
+{
+ run_loopback_duplex_test(true);
+ run_loopback_duplex_test(false);
+}
+
+void run_loopback_separate_streams_test(bool is_float)
+{
+ cubeb * ctx;
+ cubeb_stream * input_stream;
+ cubeb_stream * output_stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb loopback example: separate streams");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ if (!can_run_audio_input_test(ctx)) {
+ return;
+ }
+
+ input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = 1;
+ input_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK;
+ output_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ output_params.rate = SAMPLE_FREQUENCY;
+ output_params.channels = 1;
+ output_params.layout = CUBEB_LAYOUT_MONO;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ std::unique_ptr<user_state_loopback> user_data(new user_state_loopback());
+ ASSERT_TRUE(!!user_data) << "Error allocating user data";
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ /* setup an input stream with loopback */
+ r = cubeb_stream_init(ctx, &input_stream, "Cubeb loopback input only",
+ NULL, &input_params, NULL, NULL, latency_frames,
+ is_float ? data_cb_loop_input_only<float> : data_cb_loop_input_only<short>,
+ state_cb_loop, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy);
+
+ /* setup an output stream */
+ r = cubeb_stream_init(ctx, &output_stream, "Cubeb loopback output only",
+ NULL, NULL, NULL, &output_params, latency_frames,
+ is_float ? data_cb_playback<float> : data_cb_playback<short>,
+ state_cb_loop, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_output_stream_at_exit(output_stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(input_stream);
+ cubeb_stream_start(output_stream);
+ delay(300);
+ cubeb_stream_stop(output_stream);
+ cubeb_stream_stop(input_stream);
+
+ /* access after stop should not happen, but lock just in case and to appease sanitization tools */
+ std::lock_guard<std::mutex> lock(user_data->user_state_mutex);
+ std::vector<double> & output_frames = user_data->output_frames;
+ std::vector<double> & input_frames = user_data->input_frames;
+ ASSERT_LE(output_frames.size(), input_frames.size())
+ << "#Output frames should be less or equal to #input frames";
+
+ size_t phase = find_phase(user_data->output_frames, user_data->input_frames, NUM_FRAMES_TO_OUTPUT);
+
+ /* extract vectors of just the relevant signal from output and input */
+ auto output_frames_signal_start = output_frames.begin();
+ auto output_frames_signal_end = output_frames.begin() + NUM_FRAMES_TO_OUTPUT;
+ std::vector<double> trimmed_output_frames(output_frames_signal_start, output_frames_signal_end);
+ auto input_frames_signal_start = input_frames.begin() + phase;
+ auto input_frames_signal_end = input_frames.begin() + phase + NUM_FRAMES_TO_OUTPUT;
+ std::vector<double> trimmed_input_frames(input_frames_signal_start, input_frames_signal_end);
+
+ compare_signals(trimmed_output_frames, trimmed_input_frames);
+}
+
+TEST(cubeb, loopback_separate_streams)
+{
+ run_loopback_separate_streams_test(true);
+ run_loopback_separate_streams_test(false);
+}
+
+void run_loopback_silence_test(bool is_float)
+{
+ cubeb * ctx;
+ cubeb_stream * input_stream;
+ cubeb_stream_params input_params;
+ int r;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb loopback example: record silence");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ if (!can_run_audio_input_test(ctx)) {
+ return;
+ }
+
+ input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = 1;
+ input_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK;
+
+ std::unique_ptr<user_state_loopback> user_data(new user_state_loopback());
+ ASSERT_TRUE(!!user_data) << "Error allocating user data";
+
+ r = cubeb_get_min_latency(ctx, &input_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ /* setup an input stream with loopback */
+ r = cubeb_stream_init(ctx, &input_stream, "Cubeb loopback input only",
+ NULL, &input_params, NULL, NULL, latency_frames,
+ is_float ? data_cb_loop_input_only<float> : data_cb_loop_input_only<short>,
+ state_cb_loop, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(input_stream);
+ delay(300);
+ cubeb_stream_stop(input_stream);
+
+ /* access after stop should not happen, but lock just in case and to appease sanitization tools */
+ std::lock_guard<std::mutex> lock(user_data->user_state_mutex);
+ std::vector<double> & input_frames = user_data->input_frames;
+
+ /* expect to have at least ~50ms of frames */
+ ASSERT_GE(input_frames.size(), SAMPLE_FREQUENCY / 20);
+ double EPISILON = 0.0001;
+ /* frames should be 0.0, but use epsilon to avoid possible issues with impls
+ that may use ~0.0 silence values. */
+ for (double frame : input_frames) {
+ ASSERT_LT(abs(frame), EPISILON);
+ }
+}
+
+TEST(cubeb, loopback_silence)
+{
+ run_loopback_silence_test(true);
+ run_loopback_silence_test(false);
+}
+
+void run_loopback_device_selection_test(bool is_float)
+{
+ cubeb * ctx;
+ cubeb_device_collection collection;
+ cubeb_stream * input_stream;
+ cubeb_stream * output_stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb loopback example: device selection, separate streams");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ if (!can_run_audio_input_test(ctx)) {
+ return;
+ }
+
+ r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection);
+ if (r == CUBEB_ERROR_NOT_SUPPORTED) {
+ fprintf(stderr, "Device enumeration not supported"
+ " for this backend, skipping this test.\n");
+ return;
+ }
+
+ ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;
+ /* get first preferred output device id */
+ std::string device_id;
+ for (size_t i = 0; i < collection.count; i++) {
+ if (collection.device[i].preferred) {
+ device_id = collection.device[i].device_id;
+ break;
+ }
+ }
+ cubeb_device_collection_destroy(ctx, &collection);
+ if (device_id.empty()) {
+ fprintf(stderr, "Could not find preferred device, aborting test.\n");
+ return;
+ }
+
+ input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = 1;
+ input_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK;
+ output_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ output_params.rate = SAMPLE_FREQUENCY;
+ output_params.channels = 1;
+ output_params.layout = CUBEB_LAYOUT_MONO;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ std::unique_ptr<user_state_loopback> user_data(new user_state_loopback());
+ ASSERT_TRUE(!!user_data) << "Error allocating user data";
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ /* setup an input stream with loopback */
+ r = cubeb_stream_init(ctx, &input_stream, "Cubeb loopback input only",
+ device_id.c_str(), &input_params, NULL, NULL, latency_frames,
+ is_float ? data_cb_loop_input_only<float> : data_cb_loop_input_only<short>,
+ state_cb_loop, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy);
+
+ /* setup an output stream */
+ r = cubeb_stream_init(ctx, &output_stream, "Cubeb loopback output only",
+ NULL, NULL, device_id.c_str(), &output_params, latency_frames,
+ is_float ? data_cb_playback<float> : data_cb_playback<short>,
+ state_cb_loop, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_output_stream_at_exit(output_stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(input_stream);
+ cubeb_stream_start(output_stream);
+ delay(300);
+ cubeb_stream_stop(output_stream);
+ cubeb_stream_stop(input_stream);
+
+ /* access after stop should not happen, but lock just in case and to appease sanitization tools */
+ std::lock_guard<std::mutex> lock(user_data->user_state_mutex);
+ std::vector<double> & output_frames = user_data->output_frames;
+ std::vector<double> & input_frames = user_data->input_frames;
+ ASSERT_LE(output_frames.size(), input_frames.size())
+ << "#Output frames should be less or equal to #input frames";
+
+ size_t phase = find_phase(user_data->output_frames, user_data->input_frames, NUM_FRAMES_TO_OUTPUT);
+
+ /* extract vectors of just the relevant signal from output and input */
+ auto output_frames_signal_start = output_frames.begin();
+ auto output_frames_signal_end = output_frames.begin() + NUM_FRAMES_TO_OUTPUT;
+ std::vector<double> trimmed_output_frames(output_frames_signal_start, output_frames_signal_end);
+ auto input_frames_signal_start = input_frames.begin() + phase;
+ auto input_frames_signal_end = input_frames.begin() + phase + NUM_FRAMES_TO_OUTPUT;
+ std::vector<double> trimmed_input_frames(input_frames_signal_start, input_frames_signal_end);
+
+ compare_signals(trimmed_output_frames, trimmed_input_frames);
+}
+
+TEST(cubeb, loopback_device_selection)
+{
+ run_loopback_device_selection_test(true);
+ run_loopback_device_selection_test(false);
+}
diff --git a/media/libcubeb/test/test_overload_callback.cpp b/media/libcubeb/test/test_overload_callback.cpp
new file mode 100644
index 0000000000..c8f4dafd17
--- /dev/null
+++ b/media/libcubeb/test/test_overload_callback.cpp
@@ -0,0 +1,101 @@
+/*
+ * Copyright © 2017 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <memory>
+#include <atomic>
+#include "cubeb/cubeb.h"
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+#define SAMPLE_FREQUENCY 48000
+#define STREAM_FORMAT CUBEB_SAMPLE_S16LE
+
+std::atomic<bool> load_callback{ false };
+
+long data_cb(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ if (load_callback) {
+ fprintf(stderr, "Sleeping...\n");
+ delay(100000);
+ fprintf(stderr, "Sleeping done\n");
+ }
+ return nframes;
+}
+
+void state_cb(cubeb_stream * stream, void * /*user*/, cubeb_state state)
+{
+ ASSERT_TRUE(!!stream);
+
+ switch (state) {
+ case CUBEB_STATE_STARTED:
+ fprintf(stderr, "stream started\n"); break;
+ case CUBEB_STATE_STOPPED:
+ fprintf(stderr, "stream stopped\n"); break;
+ case CUBEB_STATE_DRAINED:
+ FAIL() << "this test is not supposed to drain"; break;
+ case CUBEB_STATE_ERROR:
+ fprintf(stderr, "stream error\n"); break;
+ default:
+ FAIL() << "this test is not supposed to have a weird state"; break;
+ }
+}
+
+TEST(cubeb, overload_callback)
+{
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params output_params;
+ int r;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb callback overload");
+ ASSERT_EQ(r, CUBEB_OK);
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ // This test is specifically designed to test a behaviour of the WASAPI
+ // backend in a specific scenario.
+ if (strcmp(cubeb_get_backend_id(ctx), "wasapi") != 0) {
+ return;
+ }
+
+ output_params.format = STREAM_FORMAT;
+ output_params.rate = 48000;
+ output_params.channels = 2;
+ output_params.layout = CUBEB_LAYOUT_STEREO;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ r = cubeb_stream_init(ctx, &stream, "Cubeb",
+ NULL, NULL, NULL, &output_params,
+ latency_frames, data_cb, state_cb, NULL);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(stream);
+ delay(500);
+ // This causes the callback to sleep for a large number of seconds.
+ load_callback = true;
+ delay(500);
+ cubeb_stream_stop(stream);
+}
+
+#undef SAMPLE_FREQUENCY
+#undef STREAM_FORMAT
diff --git a/media/libcubeb/test/test_record.cpp b/media/libcubeb/test/test_record.cpp
new file mode 100644
index 0000000000..ba0c758721
--- /dev/null
+++ b/media/libcubeb/test/test_record.cpp
@@ -0,0 +1,119 @@
+/*
+ * 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. */
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <memory>
+#include "cubeb/cubeb.h"
+#include <atomic>
+
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+#define SAMPLE_FREQUENCY 48000
+#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
+
+struct user_state_record
+{
+ std::atomic<int> invalid_audio_value{ 0 };
+};
+
+long data_cb_record(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ user_state_record * u = reinterpret_cast<user_state_record*>(user);
+ float *b = (float *)inputbuffer;
+
+ if (stream == NULL || inputbuffer == NULL || outputbuffer != NULL) {
+ return CUBEB_ERROR;
+ }
+
+ for (long i = 0; i < nframes; i++) {
+ if (b[i] <= -1.0 || b[i] >= 1.0) {
+ u->invalid_audio_value = 1;
+ break;
+ }
+ }
+
+ return nframes;
+}
+
+void state_cb_record(cubeb_stream * stream, void * /*user*/, cubeb_state state)
+{
+ if (stream == NULL)
+ return;
+
+ switch (state) {
+ case CUBEB_STATE_STARTED:
+ fprintf(stderr, "stream started\n"); break;
+ case CUBEB_STATE_STOPPED:
+ fprintf(stderr, "stream stopped\n"); break;
+ case CUBEB_STATE_DRAINED:
+ fprintf(stderr, "stream drained\n"); break;
+ default:
+ fprintf(stderr, "unknown stream state %d\n", state);
+ }
+
+ return;
+}
+
+TEST(cubeb, record)
+{
+ if (cubeb_set_log_callback(CUBEB_LOG_DISABLED, nullptr /*print_log*/) != CUBEB_OK) {
+ fprintf(stderr, "Set log callback failed\n");
+ }
+ cubeb *ctx;
+ cubeb_stream *stream;
+ cubeb_stream_params params;
+ int r;
+ user_state_record stream_state;
+
+ r = common_init(&ctx, "Cubeb record example");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ /* This test needs an available input device, skip it if this host does not
+ * have one. */
+ if (!can_run_audio_input_test(ctx)) {
+ return;
+ }
+
+ params.format = STREAM_FORMAT;
+ params.rate = SAMPLE_FREQUENCY;
+ params.channels = 1;
+ params.layout = CUBEB_LAYOUT_UNDEFINED;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_stream_init(ctx, &stream, "Cubeb record (mono)", NULL, &params, NULL, nullptr,
+ 4096, data_cb_record, state_cb_record, &stream_state);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(stream);
+ delay(500);
+ cubeb_stream_stop(stream);
+
+#ifdef __linux__
+ // user callback does not arrive in Linux, silence the error
+ fprintf(stderr, "Check is disabled in Linux\n");
+#else
+ ASSERT_FALSE(stream_state.invalid_audio_value.load());
+#endif
+}
+
+#undef SAMPLE_FREQUENCY
+#undef STREAM_FORMAT
diff --git a/media/libcubeb/test/test_resampler.cpp b/media/libcubeb/test/test_resampler.cpp
new file mode 100644
index 0000000000..3d55db66ed
--- /dev/null
+++ b/media/libcubeb/test/test_resampler.cpp
@@ -0,0 +1,1089 @@
+/*
+ * 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
+#include "gtest/gtest.h"
+#include "common.h"
+#include "cubeb_resampler_internal.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, rate);
+ 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_EQ(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_TRUE(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_resampler(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;
+ input_params.prefs = output_params.prefs = CUBEB_STREAM_PREF_NONE;
+ long got;
+
+ cubeb_resampler * resampler =
+ cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params, target_rate,
+ data_cb_resampler, (void*)&state, CUBEB_RESAMPLER_QUALITY_VOIP,
+ CUBEB_RESAMPLER_RECLOCK_NONE);
+
+ 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());
+
+ // This is disabled because the latency estimation in the resampler code is
+ // slightly off so we can generate expected vectors.
+ // See https://github.com/kinetiknz/cubeb/issues/93
+ // ASSERT_TRUE(array_fuzzy_equal(state.input, expected_resampled_input, epsilon<T>(input_rate/target_rate)));
+ // ASSERT_TRUE(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]))
+
+TEST(cubeb, resampler_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) {
+ fprintf(stderr, "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);
+ }
+ }
+ }
+ }
+}
+
+TEST(cubeb, DISABLED_resampler_duplex)
+{
+ 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) {
+ fprintf(stderr, "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);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+TEST(cubeb, resampler_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++) {
+ fprintf(stderr, "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)
+{
+ EXPECT_TRUE(output_buffer);
+ EXPECT_TRUE(!input_buffer);
+ return frame_count;
+}
+
+TEST(cubeb, resampler_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,
+ CUBEB_RESAMPLER_RECLOCK_NONE);
+ const long out_frames = 128;
+ float out_buffer[out_frames];
+ long got;
+
+ got = cubeb_resampler_fill(resampler, nullptr, nullptr,
+ out_buffer, out_frames);
+
+ ASSERT_EQ(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)
+{
+ EXPECT_TRUE(output_buffer);
+ EXPECT_TRUE(!input_buffer);
+ auto cb_count = static_cast<int *>(user_ptr);
+ (*cb_count)++;
+ return frame_count - 1;
+}
+
+TEST(cubeb, 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;
+ int cb_count = 0;
+
+ cubeb_resampler * resampler =
+ cubeb_resampler_create((cubeb_stream*)nullptr, nullptr, &output_params, target_rate,
+ test_drain_data_cb, &cb_count,
+ CUBEB_RESAMPLER_QUALITY_VOIP,
+ CUBEB_RESAMPLER_RECLOCK_NONE);
+
+ 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);
+
+ /* The callback should be called once but not again after returning <
+ * frame_count. */
+ ASSERT_EQ(cb_count, 1);
+
+ cubeb_resampler_destroy(resampler);
+}
+
+// gtest does not support using ASSERT_EQ and friend in a function that returns
+// a value.
+void check_output(const void * input_buffer, void * output_buffer, long frame_count)
+{
+ ASSERT_EQ(input_buffer, nullptr);
+ ASSERT_EQ(frame_count, 256);
+ ASSERT_TRUE(!!output_buffer);
+}
+
+long cb_passthrough_resampler_output(cubeb_stream * /*stm*/, void * /*user_ptr*/,
+ const void * input_buffer,
+ void * output_buffer, long frame_count)
+{
+ check_output(input_buffer, output_buffer, frame_count);
+ return frame_count;
+}
+
+TEST(cubeb, resampler_passthrough_output_only)
+{
+ // Test that the passthrough resampler works when there is only an output stream.
+ cubeb_stream_params output_params;
+
+ const size_t output_channels = 2;
+ output_params.channels = output_channels;
+ output_params.rate = 44100;
+ output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ int target_rate = output_params.rate;
+
+ cubeb_resampler * resampler =
+ cubeb_resampler_create((cubeb_stream*)nullptr, nullptr, &output_params,
+ target_rate, cb_passthrough_resampler_output, nullptr,
+ CUBEB_RESAMPLER_QUALITY_VOIP,
+ CUBEB_RESAMPLER_RECLOCK_NONE);
+
+ float output_buffer[output_channels * 256];
+
+ long got;
+ for (uint32_t i = 0; i < 30; i++) {
+ got = cubeb_resampler_fill(resampler, nullptr, nullptr, output_buffer, 256);
+ ASSERT_EQ(got, 256);
+ }
+
+ cubeb_resampler_destroy(resampler);
+}
+
+// gtest does not support using ASSERT_EQ and friend in a function that returns
+// a value.
+void check_input(const void * input_buffer, void * output_buffer, long frame_count)
+{
+ ASSERT_EQ(output_buffer, nullptr);
+ ASSERT_EQ(frame_count, 256);
+ ASSERT_TRUE(!!input_buffer);
+}
+
+long cb_passthrough_resampler_input(cubeb_stream * /*stm*/, void * /*user_ptr*/,
+ const void * input_buffer,
+ void * output_buffer, long frame_count)
+{
+ check_input(input_buffer, output_buffer, frame_count);
+ return frame_count;
+}
+
+TEST(cubeb, resampler_passthrough_input_only)
+{
+ // Test that the passthrough resampler works when there is only an output stream.
+ cubeb_stream_params input_params;
+
+ const size_t input_channels = 2;
+ input_params.channels = input_channels;
+ input_params.rate = 44100;
+ input_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ int target_rate = input_params.rate;
+
+ cubeb_resampler * resampler =
+ cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, nullptr,
+ target_rate, cb_passthrough_resampler_input, nullptr,
+ CUBEB_RESAMPLER_QUALITY_VOIP,
+ CUBEB_RESAMPLER_RECLOCK_NONE);
+
+ float input_buffer[input_channels * 256];
+
+ long got;
+ for (uint32_t i = 0; i < 30; i++) {
+ long int frames = 256;
+ got = cubeb_resampler_fill(resampler, input_buffer, &frames, nullptr, 0);
+ ASSERT_EQ(got, 256);
+ }
+
+ cubeb_resampler_destroy(resampler);
+}
+
+template<typename T>
+long seq(T* array, int stride, long start, long count)
+{
+ uint32_t output_idx = 0;
+ for(int i = 0; i < count; i++) {
+ for (int j = 0; j < stride; j++) {
+ array[output_idx + j] = static_cast<T>(start + i);
+ }
+ output_idx += stride;
+ }
+ return start + count;
+}
+
+template<typename T>
+void is_seq(T * array, int stride, long count, long expected_start)
+{
+ uint32_t output_index = 0;
+ for (long i = 0; i < count; i++) {
+ for (int j = 0; j < stride; j++) {
+ ASSERT_EQ(array[output_index + j], expected_start + i);
+ }
+ output_index += stride;
+ }
+}
+
+template<typename T>
+void is_not_seq(T * array, int stride, long count, long expected_start)
+{
+ uint32_t output_index = 0;
+ for (long i = 0; i < count; i++) {
+ for (int j = 0; j < stride; j++) {
+ ASSERT_NE(array[output_index + j], expected_start + i);
+ }
+ output_index += stride;
+ }
+}
+
+struct closure {
+ int input_channel_count;
+};
+
+// gtest does not support using ASSERT_EQ and friend in a function that returns
+// a value.
+template<typename T>
+void check_duplex(const T * input_buffer,
+ T * output_buffer, long frame_count,
+ int input_channel_count)
+{
+ ASSERT_EQ(frame_count, 256);
+ // Silence scan-build warning.
+ ASSERT_TRUE(!!output_buffer); assert(output_buffer);
+ ASSERT_TRUE(!!input_buffer); assert(input_buffer);
+
+ int output_index = 0;
+ int input_index = 0;
+ for (int i = 0; i < frame_count; i++) {
+ // output is two channels, input one or two channels.
+ if (input_channel_count == 1) {
+ output_buffer[output_index] = output_buffer[output_index + 1] = input_buffer[i];
+ } else if (input_channel_count == 2) {
+ output_buffer[output_index] = input_buffer[input_index];
+ output_buffer[output_index + 1] = input_buffer[input_index + 1];
+ }
+ output_index += 2;
+ input_index += input_channel_count;
+ }
+}
+
+long cb_passthrough_resampler_duplex(cubeb_stream * /*stm*/, void * user_ptr,
+ const void * input_buffer,
+ void * output_buffer, long frame_count)
+{
+ closure * c = reinterpret_cast<closure*>(user_ptr);
+ check_duplex<float>(static_cast<const float*>(input_buffer),
+ static_cast<float*>(output_buffer),
+ frame_count, c->input_channel_count);
+ return frame_count;
+}
+
+
+TEST(cubeb, resampler_passthrough_duplex_callback_reordering)
+{
+ // Test that when pre-buffering on resampler creation, we can survive an input
+ // callback being delayed.
+
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+
+ const int input_channels = 1;
+ const int output_channels = 2;
+
+ input_params.channels = input_channels;
+ input_params.rate = 44100;
+ input_params.format = CUBEB_SAMPLE_FLOAT32NE;
+
+ output_params.channels = output_channels;
+ output_params.rate = input_params.rate;
+ output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+
+ int target_rate = input_params.rate;
+
+ closure c;
+ c.input_channel_count = input_channels;
+
+ cubeb_resampler * resampler =
+ cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params,
+ target_rate, cb_passthrough_resampler_duplex, &c,
+ CUBEB_RESAMPLER_QUALITY_VOIP,
+ CUBEB_RESAMPLER_RECLOCK_NONE);
+
+ const long BUF_BASE_SIZE = 256;
+ float input_buffer_prebuffer[input_channels * BUF_BASE_SIZE * 2];
+ float input_buffer_glitch[input_channels * BUF_BASE_SIZE * 2];
+ float input_buffer_normal[input_channels * BUF_BASE_SIZE];
+ float output_buffer[output_channels * BUF_BASE_SIZE];
+
+ long seq_idx = 0;
+ long output_seq_idx = 0;
+
+ long prebuffer_frames = ARRAY_LENGTH(input_buffer_prebuffer) / input_params.channels;
+ seq_idx = seq(input_buffer_prebuffer, input_channels, seq_idx,
+ prebuffer_frames);
+
+ long got = cubeb_resampler_fill(resampler, input_buffer_prebuffer, &prebuffer_frames,
+ output_buffer, BUF_BASE_SIZE);
+
+ output_seq_idx += BUF_BASE_SIZE;
+
+ // prebuffer_frames will hold the frames used by the resampler.
+ ASSERT_EQ(prebuffer_frames, BUF_BASE_SIZE);
+ ASSERT_EQ(got, BUF_BASE_SIZE);
+
+ for (uint32_t i = 0; i < 300; i++) {
+ long int frames = BUF_BASE_SIZE;
+ // Simulate that sometimes, we don't have the input callback on time
+ if (i != 0 && (i % 100) == 0) {
+ long zero = 0;
+ got = cubeb_resampler_fill(resampler, input_buffer_normal /* unused here */,
+ &zero, output_buffer, BUF_BASE_SIZE);
+ is_seq(output_buffer, 2, BUF_BASE_SIZE, output_seq_idx);
+ output_seq_idx += BUF_BASE_SIZE;
+ } else if (i != 0 && (i % 100) == 1) {
+ // if this is the case, the on the next iteration, we'll have twice the
+ // amount of input frames
+ seq_idx = seq(input_buffer_glitch, input_channels, seq_idx, BUF_BASE_SIZE * 2);
+ frames = 2 * BUF_BASE_SIZE;
+ got = cubeb_resampler_fill(resampler, input_buffer_glitch, &frames, output_buffer, BUF_BASE_SIZE);
+ is_seq(output_buffer, 2, BUF_BASE_SIZE, output_seq_idx);
+ output_seq_idx += BUF_BASE_SIZE;
+ } else {
+ // normal case
+ seq_idx = seq(input_buffer_normal, input_channels, seq_idx, BUF_BASE_SIZE);
+ long normal_input_frame_count = 256;
+ got = cubeb_resampler_fill(resampler, input_buffer_normal, &normal_input_frame_count, output_buffer, BUF_BASE_SIZE);
+ is_seq(output_buffer, 2, BUF_BASE_SIZE, output_seq_idx);
+ output_seq_idx += BUF_BASE_SIZE;
+ }
+ ASSERT_EQ(got, BUF_BASE_SIZE);
+ }
+
+ cubeb_resampler_destroy(resampler);
+}
+
+// Artificially simulate output thread underruns,
+// by building up artificial delay in the input.
+// Check that the frame drop logic kicks in.
+TEST(cubeb, resampler_drift_drop_data)
+{
+ for (uint32_t input_channels = 1; input_channels < 3; input_channels++) {
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+
+ const int output_channels = 2;
+ const int sample_rate = 44100;
+
+ input_params.channels = input_channels;
+ input_params.rate = sample_rate;
+ input_params.format = CUBEB_SAMPLE_FLOAT32NE;
+
+ output_params.channels = output_channels;
+ output_params.rate = sample_rate;
+ output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+
+ int target_rate = input_params.rate;
+
+ closure c;
+ c.input_channel_count = input_channels;
+
+ cubeb_resampler * resampler =
+ cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params,
+ target_rate, cb_passthrough_resampler_duplex, &c,
+ CUBEB_RESAMPLER_QUALITY_VOIP, CUBEB_RESAMPLER_RECLOCK_NONE);
+
+ const long BUF_BASE_SIZE = 256;
+
+ // The factor by which the deadline is missed. This is intentionally
+ // kind of large to trigger the frame drop quickly. In real life, multiple
+ // smaller under-runs would accumulate.
+ const long UNDERRUN_FACTOR = 10;
+ // Number buffer used for pre-buffering, that some backends do.
+ const long PREBUFFER_FACTOR = 2;
+
+ std::vector<float> input_buffer_prebuffer(input_channels * BUF_BASE_SIZE * PREBUFFER_FACTOR);
+ std::vector<float> input_buffer_glitch(input_channels * BUF_BASE_SIZE * UNDERRUN_FACTOR);
+ std::vector<float> input_buffer_normal(input_channels * BUF_BASE_SIZE);
+ std::vector<float> output_buffer(output_channels * BUF_BASE_SIZE);
+
+ long seq_idx = 0;
+ long output_seq_idx = 0;
+
+ long prebuffer_frames = input_buffer_prebuffer.size() / input_params.channels;
+ seq_idx = seq(input_buffer_prebuffer.data(), input_channels, seq_idx,
+ prebuffer_frames);
+
+ long got = cubeb_resampler_fill(resampler, input_buffer_prebuffer.data(), &prebuffer_frames,
+ output_buffer.data(), BUF_BASE_SIZE);
+
+ output_seq_idx += BUF_BASE_SIZE;
+
+ // prebuffer_frames will hold the frames used by the resampler.
+ ASSERT_EQ(prebuffer_frames, BUF_BASE_SIZE);
+ ASSERT_EQ(got, BUF_BASE_SIZE);
+
+ for (uint32_t i = 0; i < 300; i++) {
+ long int frames = BUF_BASE_SIZE;
+ if (i != 0 && (i % 100) == 1) {
+ // Once in a while, the output thread misses its deadline.
+ // The input thread still produces data, so it ends up accumulating. Simulate this by providing a
+ // much bigger input buffer. Check that the sequence is now unaligned, meaning we've dropped data
+ // to keep everything in sync.
+ seq_idx = seq(input_buffer_glitch.data(), input_channels, seq_idx, BUF_BASE_SIZE * UNDERRUN_FACTOR);
+ frames = BUF_BASE_SIZE * UNDERRUN_FACTOR;
+ got = cubeb_resampler_fill(resampler, input_buffer_glitch.data(), &frames, output_buffer.data(), BUF_BASE_SIZE);
+ is_seq(output_buffer.data(), 2, BUF_BASE_SIZE, output_seq_idx);
+ output_seq_idx += BUF_BASE_SIZE;
+ }
+ else if (i != 0 && (i % 100) == 2) {
+ // On the next iteration, the sequence should be broken
+ seq_idx = seq(input_buffer_normal.data(), input_channels, seq_idx, BUF_BASE_SIZE);
+ long normal_input_frame_count = 256;
+ got = cubeb_resampler_fill(resampler, input_buffer_normal.data(), &normal_input_frame_count, output_buffer.data(), BUF_BASE_SIZE);
+ is_not_seq(output_buffer.data(), output_channels, BUF_BASE_SIZE, output_seq_idx);
+ // Reclock so that we can use is_seq again.
+ output_seq_idx = output_buffer[BUF_BASE_SIZE * output_channels - 1] + 1;
+ }
+ else {
+ // normal case
+ seq_idx = seq(input_buffer_normal.data(), input_channels, seq_idx, BUF_BASE_SIZE);
+ long normal_input_frame_count = 256;
+ got = cubeb_resampler_fill(resampler, input_buffer_normal.data(), &normal_input_frame_count, output_buffer.data(), BUF_BASE_SIZE);
+ is_seq(output_buffer.data(), output_channels, BUF_BASE_SIZE, output_seq_idx);
+ output_seq_idx += BUF_BASE_SIZE;
+ }
+ ASSERT_EQ(got, BUF_BASE_SIZE);
+ }
+
+ cubeb_resampler_destroy(resampler);
+ }
+}
+
+static long
+passthrough_resampler_fill_eq_input(cubeb_stream * stream,
+ void * user_ptr,
+ void const * input_buffer,
+ void * output_buffer,
+ long nframes) {
+ // gtest does not support using ASSERT_EQ and friends in a
+ // function that returns a value.
+ [nframes, input_buffer]() {
+ ASSERT_EQ(nframes, 32);
+ const float* input = static_cast<const float*>(input_buffer);
+ for (int i = 0; i < 64; ++i) {
+ ASSERT_FLOAT_EQ(input[i], 0.01 * i);
+ }
+ }();
+ return nframes;
+}
+
+TEST(cubeb, passthrough_resampler_fill_eq_input) {
+ uint32_t channels = 2;
+ uint32_t sample_rate = 44100;
+ passthrough_resampler<float> resampler =
+ passthrough_resampler<float>(nullptr, passthrough_resampler_fill_eq_input,
+ nullptr, channels, sample_rate);
+
+ long input_frame_count = 32;
+ long output_frame_count = 32;
+ float input[64] = {};
+ float output[64] = {};
+ for (uint32_t i = 0; i < input_frame_count * channels; ++i) {
+ input[i] = 0.01 * i;
+ }
+ long got = resampler.fill(input, &input_frame_count, output, output_frame_count);
+ ASSERT_EQ(got, output_frame_count);
+ // Input frames used must be equal to output frames.
+ ASSERT_EQ(input_frame_count, output_frame_count);
+}
+
+static long
+passthrough_resampler_fill_short_input(cubeb_stream * stream,
+ void * user_ptr,
+ void const * input_buffer,
+ void * output_buffer,
+ long nframes) {
+ // gtest does not support using ASSERT_EQ and friends in a
+ // function that returns a value.
+ [nframes, input_buffer]() {
+ ASSERT_EQ(nframes, 32);
+ const float* input = static_cast<const float*>(input_buffer);
+ // First part contains the input
+ for (int i = 0; i < 32; ++i) {
+ ASSERT_FLOAT_EQ(input[i], 0.01 * i);
+ }
+ // missing part contains silence
+ for (int i = 32; i < 64; ++i) {
+ ASSERT_FLOAT_EQ(input[i], 0.0);
+ }
+ }();
+ return nframes;
+}
+
+TEST(cubeb, passthrough_resampler_fill_short_input) {
+ uint32_t channels = 2;
+ uint32_t sample_rate = 44100;
+ passthrough_resampler<float> resampler =
+ passthrough_resampler<float>(nullptr, passthrough_resampler_fill_short_input,
+ nullptr, channels, sample_rate);
+
+ long input_frame_count = 16;
+ long output_frame_count = 32;
+ float input[64] = {};
+ float output[64] = {};
+ for (uint32_t i = 0; i < input_frame_count * channels; ++i) {
+ input[i] = 0.01 * i;
+ }
+ long got = resampler.fill(input, &input_frame_count, output, output_frame_count);
+ ASSERT_EQ(got, output_frame_count);
+ // Input frames used are less than the output frames due to glitch.
+ ASSERT_EQ(input_frame_count, output_frame_count - 16);
+}
+
+static long
+passthrough_resampler_fill_input_left(cubeb_stream * stream,
+ void * user_ptr,
+ void const * input_buffer,
+ void * output_buffer,
+ long nframes) {
+ // gtest does not support using ASSERT_EQ and friends in a
+ // function that returns a value.
+ int iteration = *static_cast<int*>(user_ptr);
+ if (iteration == 1) {
+ [nframes, input_buffer]() {
+ ASSERT_EQ(nframes, 32);
+ const float* input = static_cast<const float*>(input_buffer);
+ for (int i = 0; i < 64; ++i) {
+ ASSERT_FLOAT_EQ(input[i], 0.01 * i);
+ }
+ }();
+ } else if (iteration == 2) {
+ [nframes, input_buffer]() {
+ ASSERT_EQ(nframes, 32);
+ const float* input = static_cast<const float*>(input_buffer);
+ for (int i = 0; i < 32; ++i) {
+ // First part contains the reamaining input samples from previous
+ // iteration (since they were more).
+ ASSERT_FLOAT_EQ(input[i], 0.01 * (i + 64));
+ // next part contains the new buffer
+ ASSERT_FLOAT_EQ(input[i + 32], 0.01 * i);
+ }
+ }();
+ } else if (iteration == 3) {
+ [nframes, input_buffer]() {
+ ASSERT_EQ(nframes, 32);
+ const float* input = static_cast<const float*>(input_buffer);
+ for (int i = 0; i < 32; ++i) {
+ // First part (16 frames) contains the reamaining input samples
+ // from previous iteration (since they were more).
+ ASSERT_FLOAT_EQ(input[i], 0.01 * (i + 32));
+ }
+ for (int i = 0; i < 16; ++i) {
+ // next part (8 frames) contains the new input buffer.
+ ASSERT_FLOAT_EQ(input[i + 32], 0.01 * i);
+ // last part (8 frames) contains silence.
+ ASSERT_FLOAT_EQ(input[i + 32 + 16], 0.0);
+ }
+ }();
+ }
+ return nframes;
+}
+
+TEST(cubeb, passthrough_resampler_fill_input_left) {
+ const uint32_t channels = 2;
+ const uint32_t sample_rate = 44100;
+ int iteration = 0;
+ passthrough_resampler<float> resampler =
+ passthrough_resampler<float>(nullptr, passthrough_resampler_fill_input_left,
+ &iteration, channels, sample_rate);
+
+ long input_frame_count = 48; // 32 + 16
+ const long output_frame_count = 32;
+ float input[96] = {};
+ float output[64] = {};
+ for (uint32_t i = 0; i < input_frame_count * channels; ++i) {
+ input[i] = 0.01 * i;
+ }
+
+ // 1st iteration, add the extra input.
+ iteration = 1;
+ long got = resampler.fill(input, &input_frame_count, output, output_frame_count);
+ ASSERT_EQ(got, output_frame_count);
+ // Input frames used must be equal to output frames.
+ ASSERT_EQ(input_frame_count, output_frame_count);
+
+ // 2st iteration, use the extra input from previous iteration,
+ // 16 frames are remaining in the input buffer.
+ input_frame_count = 32; // we need 16 input frames but we get more;
+ iteration = 2;
+ got = resampler.fill(input, &input_frame_count, output, output_frame_count);
+ ASSERT_EQ(got, output_frame_count);
+ // Input frames used must be equal to output frames.
+ ASSERT_EQ(input_frame_count, output_frame_count);
+
+ // 3rd iteration, use the extra input from previous iteration.
+ // 16 frames are remaining in the input buffer.
+ input_frame_count = 16 - 8; // We need 16 more input frames but we only get 8.
+ iteration = 3;
+ got = resampler.fill(input, &input_frame_count, output, output_frame_count);
+ ASSERT_EQ(got, output_frame_count);
+ // Input frames used are less than the output frames due to glitch.
+ ASSERT_EQ(input_frame_count, output_frame_count - 8);
+}
+
+TEST(cubeb, individual_methods) {
+ const uint32_t channels = 2;
+ const uint32_t sample_rate = 44100;
+ const uint32_t frames = 256;
+
+ delay_line<float> dl(10, channels, sample_rate);
+ uint32_t frames_needed1 = dl.input_needed_for_output(0);
+ ASSERT_EQ(frames_needed1, 0u);
+
+ cubeb_resampler_speex_one_way<float> one_way(channels, sample_rate, sample_rate, CUBEB_RESAMPLER_QUALITY_DEFAULT);
+ float buffer[channels * frames] = {0.0};
+ // Add all frames in the resampler's internal buffer.
+ one_way.input(buffer, frames);
+ // Ask for less than the existing frames, this would create a uint overlflow without the fix.
+ uint32_t frames_needed2 = one_way.input_needed_for_output(0);
+ ASSERT_EQ(frames_needed2, 0u);
+}
+
+
+#undef NOMINMAX
+#undef DUMP_ARRAYS
diff --git a/media/libcubeb/test/test_ring_array.cpp b/media/libcubeb/test/test_ring_array.cpp
new file mode 100644
index 0000000000..d258d50dbe
--- /dev/null
+++ b/media/libcubeb/test/test_ring_array.cpp
@@ -0,0 +1,73 @@
+#include "gtest/gtest.h"
+#ifdef __APPLE__
+#include <string.h>
+#include <iostream>
+#include <CoreAudio/CoreAudioTypes.h>
+#include "cubeb/cubeb.h"
+#include "cubeb_ring_array.h"
+
+TEST(cubeb, ring_array)
+{
+ ring_array ra;
+
+ ASSERT_EQ(ring_array_init(&ra, 0, 0, 1, 1), CUBEB_ERROR_INVALID_PARAMETER);
+ ASSERT_EQ(ring_array_init(&ra, 1, 0, 0, 1), CUBEB_ERROR_INVALID_PARAMETER);
+
+ unsigned int capacity = 8;
+ ring_array_init(&ra, capacity, sizeof(int), 1, 1);
+ int verify_data[capacity] ;// {1,2,3,4,5,6,7,8};
+ AudioBuffer * p_data = NULL;
+
+ for (unsigned int i = 0; i < capacity; ++i) {
+ verify_data[i] = i; // in case capacity change value
+ *(int*)ra.buffer_array[i].mData = i;
+ ASSERT_EQ(ra.buffer_array[i].mDataByteSize, sizeof(int));
+ ASSERT_EQ(ra.buffer_array[i].mNumberChannels, 1u);
+ }
+
+ /* Get store buffers*/
+ for (unsigned int i = 0; i < capacity; ++i) {
+ p_data = ring_array_get_free_buffer(&ra);
+ ASSERT_NE(p_data, nullptr);
+ ASSERT_EQ(*(int*)p_data->mData, verify_data[i]);
+ }
+ /*Now array is full extra store should give NULL*/
+ ASSERT_EQ(ring_array_get_free_buffer(&ra), nullptr);
+ /* Get fetch buffers*/
+ for (unsigned int i = 0; i < capacity; ++i) {
+ p_data = ring_array_get_data_buffer(&ra);
+ ASSERT_NE(p_data, nullptr);
+ ASSERT_EQ(*(int*)p_data->mData, verify_data[i]);
+ }
+ /*Now array is empty extra fetch should give NULL*/
+ ASSERT_EQ(ring_array_get_data_buffer(&ra), nullptr);
+
+ p_data = NULL;
+ /* Repeated store fetch should can go for ever*/
+ for (unsigned int i = 0; i < 2*capacity; ++i) {
+ p_data = ring_array_get_free_buffer(&ra);
+ ASSERT_NE(p_data, nullptr);
+ ASSERT_EQ(ring_array_get_data_buffer(&ra), p_data);
+ }
+
+ p_data = NULL;
+ /* Verify/modify buffer data*/
+ for (unsigned int i = 0; i < capacity; ++i) {
+ p_data = ring_array_get_free_buffer(&ra);
+ ASSERT_NE(p_data, nullptr);
+ ASSERT_EQ(*((int*)p_data->mData), verify_data[i]);
+ (*((int*)p_data->mData))++; // Modify data
+ }
+ for (unsigned int i = 0; i < capacity; ++i) {
+ p_data = ring_array_get_data_buffer(&ra);
+ ASSERT_NE(p_data, nullptr);
+ ASSERT_EQ(*((int*)p_data->mData), verify_data[i]+1); // Verify modified data
+ }
+
+ ring_array_destroy(&ra);
+}
+#else
+TEST(cubeb, DISABLED_ring_array)
+{
+}
+#endif
diff --git a/media/libcubeb/test/test_ring_buffer.cpp b/media/libcubeb/test/test_ring_buffer.cpp
new file mode 100644
index 0000000000..cfaedd5048
--- /dev/null
+++ b/media/libcubeb/test/test_ring_buffer.cpp
@@ -0,0 +1,229 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#define NOMINMAX
+
+#include "gtest/gtest.h"
+#include "cubeb_ringbuffer.h"
+#include <iostream>
+#include <thread>
+#include <chrono>
+
+/* Generate a monotonically increasing sequence of numbers. */
+template<typename T>
+class sequence_generator
+{
+public:
+ sequence_generator(size_t channels)
+ : channels(channels)
+ { }
+ void get(T * elements, size_t frames)
+ {
+ for (size_t i = 0; i < frames; i++) {
+ for (size_t c = 0; c < channels; c++) {
+ elements[i * channels + c] = static_cast<T>(index_);
+ }
+ index_++;
+ }
+ }
+ void rewind(size_t frames)
+ {
+ index_ -= frames;
+ }
+private:
+ size_t index_ = 0;
+ size_t channels = 0;
+};
+
+/* Checks that a sequence is monotonically increasing. */
+template<typename T>
+class sequence_verifier
+{
+ public:
+ sequence_verifier(size_t channels)
+ : channels(channels)
+ { }
+ void check(T * elements, size_t frames)
+ {
+ for (size_t i = 0; i < frames; i++) {
+ for (size_t c = 0; c < channels; c++) {
+ if (elements[i * channels + c] != static_cast<T>(index_)) {
+ std::cerr << "Element " << i << " is different. Expected "
+ << static_cast<T>(index_) << ", got " << elements[i]
+ << ". (channel count: " << channels << ")." << std::endl;
+ ASSERT_TRUE(false);
+ }
+ }
+ index_++;
+ }
+ }
+ private:
+ size_t index_ = 0;
+ size_t channels = 0;
+};
+
+template<typename T>
+void test_ring(lock_free_audio_ring_buffer<T>& buf, int channels, int capacity_frames)
+{
+ std::unique_ptr<T[]> seq(new T[capacity_frames * channels]);
+ sequence_generator<T> gen(channels);
+ sequence_verifier<T> checker(channels);
+
+ int iterations = 1002;
+
+ const int block_size = 128;
+
+ while(iterations--) {
+ gen.get(seq.get(), block_size);
+ int rv = buf.enqueue(seq.get(), block_size);
+ ASSERT_EQ(rv, block_size);
+ PodZero(seq.get(), block_size);
+ rv = buf.dequeue(seq.get(), block_size);
+ ASSERT_EQ(rv, block_size);
+ checker.check(seq.get(), block_size);
+ }
+}
+
+template<typename T>
+void test_ring_multi(lock_free_audio_ring_buffer<T>& buf, int channels, int capacity_frames)
+{
+ sequence_verifier<T> checker(channels);
+ std::unique_ptr<T[]> out_buffer(new T[capacity_frames * channels]);
+
+ const int block_size = 128;
+
+ std::thread t([=, &buf] {
+ int iterations = 1002;
+ std::unique_ptr<T[]> in_buffer(new T[capacity_frames * channels]);
+ sequence_generator<T> gen(channels);
+
+ while(iterations--) {
+ std::this_thread::yield();
+ gen.get(in_buffer.get(), block_size);
+ int rv = buf.enqueue(in_buffer.get(), block_size);
+ ASSERT_TRUE(rv <= block_size);
+ if (rv != block_size) {
+ gen.rewind(block_size - rv);
+ }
+ }
+ });
+
+ int remaining = 1002;
+
+ while(remaining--) {
+ std::this_thread::yield();
+ int rv = buf.dequeue(out_buffer.get(), block_size);
+ ASSERT_TRUE(rv <= block_size);
+ checker.check(out_buffer.get(), rv);
+ }
+
+ t.join();
+}
+
+template<typename T>
+void basic_api_test(T& ring)
+{
+ ASSERT_EQ(ring.capacity(), 128);
+
+ ASSERT_EQ(ring.available_read(), 0);
+ ASSERT_EQ(ring.available_write(), 128);
+
+ int rv = ring.enqueue_default(63);
+
+ ASSERT_TRUE(rv == 63);
+ ASSERT_EQ(ring.available_read(), 63);
+ ASSERT_EQ(ring.available_write(), 65);
+
+ rv = ring.enqueue_default(65);
+
+ ASSERT_EQ(rv, 65);
+ ASSERT_EQ(ring.available_read(), 128);
+ ASSERT_EQ(ring.available_write(), 0);
+
+ rv = ring.dequeue(nullptr, 63);
+
+ ASSERT_EQ(ring.available_read(), 65);
+ ASSERT_EQ(ring.available_write(), 63);
+
+ rv = ring.dequeue(nullptr, 65);
+
+ ASSERT_EQ(ring.available_read(), 0);
+ ASSERT_EQ(ring.available_write(), 128);
+}
+
+void test_reset_api() {
+ const size_t ring_buffer_size = 128;
+ const size_t enqueue_size = ring_buffer_size / 2;
+
+ lock_free_queue<float> ring(ring_buffer_size);
+ std::thread t([=, &ring] {
+ std::unique_ptr<float[]> in_buffer(new float[enqueue_size]);
+ ring.enqueue(in_buffer.get(), enqueue_size);
+ });
+
+ t.join();
+
+ ring.reset_thread_ids();
+
+ // Enqueue with a different thread. We have reset the thread ID
+ // in the ring buffer, this should work.
+ std::thread t2([=, &ring] {
+ std::unique_ptr<float[]> in_buffer(new float[enqueue_size]);
+ ring.enqueue(in_buffer.get(), enqueue_size);
+ });
+
+ t2.join();
+
+ ASSERT_TRUE(true);
+}
+
+TEST(cubeb, ring_buffer)
+{
+ /* Basic API test. */
+ const int min_channels = 1;
+ const int max_channels = 10;
+ const int min_capacity = 199;
+ const int max_capacity = 1277;
+ const int capacity_increment = 27;
+
+ lock_free_queue<float> q1(128);
+ basic_api_test(q1);
+ lock_free_queue<short> q2(128);
+ basic_api_test(q2);
+
+ for (size_t channels = min_channels; channels < max_channels; channels++) {
+ lock_free_audio_ring_buffer<float> q3(channels, 128);
+ basic_api_test(q3);
+ lock_free_audio_ring_buffer<short> q4(channels, 128);
+ basic_api_test(q4);
+ }
+
+ /* Single thread testing. */
+ /* Test mono to 9.1 */
+ for (size_t channels = min_channels; channels < max_channels; channels++) {
+ /* Use non power-of-two numbers to catch edge-cases. */
+ for (size_t capacity_frames = min_capacity;
+ capacity_frames < max_capacity; capacity_frames+=capacity_increment) {
+ lock_free_audio_ring_buffer<float> ring(channels, capacity_frames);
+ test_ring(ring, channels, capacity_frames);
+ }
+ }
+
+ /* Multi thread testing */
+ for (size_t channels = min_channels; channels < max_channels; channels++) {
+ /* Use non power-of-two numbers to catch edge-cases. */
+ for (size_t capacity_frames = min_capacity;
+ capacity_frames < max_capacity; capacity_frames+=capacity_increment) {
+ lock_free_audio_ring_buffer<short> ring(channels, capacity_frames);
+ test_ring_multi(ring, channels, capacity_frames);
+ }
+ }
+
+ test_reset_api();
+}
+
+#undef NOMINMAX
diff --git a/media/libcubeb/test/test_sanity.cpp b/media/libcubeb/test/test_sanity.cpp
new file mode 100644
index 0000000000..d681c502bb
--- /dev/null
+++ b/media/libcubeb/test/test_sanity.cpp
@@ -0,0 +1,698 @@
+/*
+ * Copyright © 2011 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include "cubeb/cubeb.h"
+#include <atomic>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+#define STREAM_RATE 44100
+#define STREAM_LATENCY 100 * STREAM_RATE / 1000
+#define STREAM_CHANNELS 1
+#define STREAM_LAYOUT CUBEB_LAYOUT_MONO
+#define STREAM_FORMAT CUBEB_SAMPLE_S16LE
+
+int is_windows_7()
+{
+#ifdef __MINGW32__
+ fprintf(stderr, "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
+}
+
+static int dummy;
+static std::atomic<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)
+{
+ EXPECT_TRUE(stm && user_ptr == &dummy && outputbuffer && nframes > 0);
+ assert(outputbuffer);
+ memset(outputbuffer, 0, nframes * sizeof(short));
+
+ total_frames_written += nframes;
+ if (delay_callback) {
+ delay(10);
+ }
+ return nframes;
+}
+
+void
+test_state_callback(cubeb_stream * /*stm*/, void * /*user_ptr*/, cubeb_state /*state*/)
+{
+}
+
+TEST(cubeb, init_destroy_context)
+{
+ int r;
+ cubeb * ctx;
+ char const* backend_id;
+
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
+
+ backend_id = cubeb_get_backend_id(ctx);
+ ASSERT_TRUE(backend_id);
+
+ fprintf(stderr, "Backend: %s\n", backend_id);
+
+ cubeb_destroy(ctx);
+}
+
+TEST(cubeb, init_destroy_multiple_contexts)
+{
+ size_t i;
+ int r;
+ cubeb * ctx[4];
+ int order[4] = {2, 0, 3, 1};
+ ASSERT_EQ(ARRAY_LENGTH(ctx), ARRAY_LENGTH(order));
+
+ for (i = 0; i < ARRAY_LENGTH(ctx); ++i) {
+ r = common_init(&ctx[i], NULL);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx[i], nullptr);
+ }
+
+ /* destroy in a different order */
+ for (i = 0; i < ARRAY_LENGTH(ctx); ++i) {
+ cubeb_destroy(ctx[order[i]]);
+ }
+}
+
+TEST(cubeb, context_variables)
+{
+ int r;
+ cubeb * ctx;
+ uint32_t value;
+ cubeb_stream_params params;
+
+ r = common_init(&ctx, "test_context_variables");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
+
+ params.channels = STREAM_CHANNELS;
+ params.format = STREAM_FORMAT;
+ params.rate = STREAM_RATE;
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_get_min_latency(ctx, &params, &value);
+ ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
+ if (r == CUBEB_OK) {
+ ASSERT_TRUE(value > 0);
+ }
+
+ r = cubeb_get_preferred_sample_rate(ctx, &value);
+ ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
+ if (r == CUBEB_OK) {
+ ASSERT_TRUE(value > 0);
+ }
+
+ cubeb_destroy(ctx);
+}
+
+TEST(cubeb, init_destroy_stream)
+{
+ int r;
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params params;
+
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
+
+ params.format = STREAM_FORMAT;
+ params.rate = STREAM_RATE;
+ params.channels = STREAM_CHANNELS;
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
+ test_data_callback, test_state_callback, &dummy);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream, nullptr);
+
+ cubeb_stream_destroy(stream);
+ cubeb_destroy(ctx);
+}
+
+TEST(cubeb, init_destroy_multiple_streams)
+{
+ size_t i;
+ int r;
+ cubeb * ctx;
+ cubeb_stream * stream[8];
+ cubeb_stream_params params;
+
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
+
+ params.format = STREAM_FORMAT;
+ params.rate = STREAM_RATE;
+ params.channels = STREAM_CHANNELS;
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
+ r = cubeb_stream_init(ctx, &stream[i], "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
+ test_data_callback, test_state_callback, &dummy);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream[i], nullptr);
+ }
+
+ for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
+ cubeb_stream_destroy(stream[i]);
+ }
+
+ cubeb_destroy(ctx);
+}
+
+TEST(cubeb, configure_stream)
+{
+ int r;
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params params;
+
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
+
+ params.format = STREAM_FORMAT;
+ params.rate = STREAM_RATE;
+ params.channels = 2;
+ params.layout = CUBEB_LAYOUT_STEREO;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
+ test_data_callback, test_state_callback, &dummy);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream, nullptr);
+
+ r = cubeb_stream_set_volume(stream, 1.0f);
+ ASSERT_TRUE(r == 0 || r == CUBEB_ERROR_NOT_SUPPORTED);
+
+ r = cubeb_stream_set_name(stream, "test 2");
+ ASSERT_TRUE(r == 0 || r == CUBEB_ERROR_NOT_SUPPORTED);
+
+ cubeb_stream_destroy(stream);
+ cubeb_destroy(ctx);
+}
+
+TEST(cubeb, configure_stream_undefined_layout)
+{
+ int r;
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params params;
+
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
+
+ params.format = STREAM_FORMAT;
+ params.rate = STREAM_RATE;
+ params.channels = 2;
+ params.layout = CUBEB_LAYOUT_UNDEFINED;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
+ test_data_callback, test_state_callback, &dummy);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream, nullptr);
+
+ r = cubeb_stream_start(stream);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ delay(100);
+
+ r = cubeb_stream_stop(stream);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ cubeb_stream_destroy(stream);
+ cubeb_destroy(ctx);
+}
+
+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;
+
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
+
+ params.format = STREAM_FORMAT;
+ params.rate = STREAM_RATE;
+ params.channels = STREAM_CHANNELS;
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
+ r = cubeb_stream_init(ctx, &stream[i], "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
+ test_data_callback, test_state_callback, &dummy);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream[i], nullptr);
+ if (early) {
+ r = cubeb_stream_start(stream[i]);
+ ASSERT_EQ(r, CUBEB_OK);
+ }
+ }
+
+ if (!early) {
+ for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
+ r = cubeb_stream_start(stream[i]);
+ ASSERT_EQ(r, CUBEB_OK);
+ }
+ }
+
+ if (delay_ms) {
+ delay(delay_ms);
+ }
+
+ if (!early) {
+ for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
+ r = cubeb_stream_stop(stream[i]);
+ ASSERT_EQ(r, CUBEB_OK);
+ }
+ }
+
+ for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
+ if (early) {
+ r = cubeb_stream_stop(stream[i]);
+ ASSERT_EQ(r, CUBEB_OK);
+ }
+ cubeb_stream_destroy(stream[i]);
+ }
+
+ cubeb_destroy(ctx);
+}
+
+TEST(cubeb, init_start_stop_destroy_multiple_streams)
+{
+ /* 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()) {
+ 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);
+ }
+}
+
+TEST(cubeb, init_destroy_multiple_contexts_and_streams)
+{
+ 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_EQ(ARRAY_LENGTH(ctx) * streams_per_ctx, ARRAY_LENGTH(stream));
+
+ /* 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())
+ return;
+
+ params.format = STREAM_FORMAT;
+ params.rate = STREAM_RATE;
+ params.channels = STREAM_CHANNELS;
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ for (i = 0; i < ARRAY_LENGTH(ctx); ++i) {
+ r = common_init(&ctx[i], "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx[i], nullptr);
+
+ for (j = 0; j < streams_per_ctx; ++j) {
+ r = cubeb_stream_init(ctx[i], &stream[i * streams_per_ctx + j], "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
+ test_data_callback, test_state_callback, &dummy);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream[i * streams_per_ctx + j], nullptr);
+ }
+ }
+
+ 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]);
+ }
+}
+
+TEST(cubeb, basic_stream_operations)
+{
+ int r;
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params params;
+ uint64_t position;
+ uint32_t latency;
+
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
+
+ params.format = STREAM_FORMAT;
+ params.rate = STREAM_RATE;
+ params.channels = STREAM_CHANNELS;
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
+ test_data_callback, test_state_callback, &dummy);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream, nullptr);
+
+ /* position and latency before stream has started */
+ r = cubeb_stream_get_position(stream, &position);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_EQ(position, 0u);
+
+ r = cubeb_stream_get_latency(stream, &latency);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ r = cubeb_stream_start(stream);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ /* position and latency after while stream running */
+ r = cubeb_stream_get_position(stream, &position);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ r = cubeb_stream_get_latency(stream, &latency);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ r = cubeb_stream_stop(stream);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ /* position and latency after stream has stopped */
+ r = cubeb_stream_get_position(stream, &position);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ r = cubeb_stream_get_latency(stream, &latency);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ cubeb_stream_destroy(stream);
+ cubeb_destroy(ctx);
+}
+
+TEST(cubeb, stream_position)
+{
+ size_t i;
+ int r;
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params params;
+ uint64_t position, last_position;
+
+ total_frames_written = 0;
+
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
+
+ params.format = STREAM_FORMAT;
+ params.rate = STREAM_RATE;
+ params.channels = STREAM_CHANNELS;
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
+ test_data_callback, test_state_callback, &dummy);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream, nullptr);
+
+ /* stream position should not advance before starting playback */
+ r = cubeb_stream_get_position(stream, &position);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_EQ(position, 0u);
+
+ delay(500);
+
+ r = cubeb_stream_get_position(stream, &position);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_EQ(position, 0u);
+
+ /* stream position should advance during playback */
+ r = cubeb_stream_start(stream);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ /* XXX let start happen */
+ delay(500);
+
+ /* stream should have prefilled */
+ ASSERT_TRUE(total_frames_written.load() > 0);
+
+ r = cubeb_stream_get_position(stream, &position);
+ ASSERT_EQ(r, CUBEB_OK);
+ last_position = position;
+
+ delay(500);
+
+ r = cubeb_stream_get_position(stream, &position);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_GE(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_EQ(r, CUBEB_OK);
+ ASSERT_GE(position, last_position);
+ ASSERT_LE(position, total_frames_written.load());
+ 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_EQ(r, CUBEB_OK);
+ r = cubeb_stream_get_position(stream, &position);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_TRUE(last_position < position);
+ last_position = position;
+ delay(500);
+ r = cubeb_stream_start(stream);
+ ASSERT_EQ(r, CUBEB_OK);
+ delay(500);
+ }
+
+ ASSERT_NE(last_position, 0u);
+
+ /* stream position should not advance after stopping playback */
+ r = cubeb_stream_stop(stream);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ /* XXX allow stream to settle */
+ delay(500);
+
+ r = cubeb_stream_get_position(stream, &position);
+ ASSERT_EQ(r, CUBEB_OK);
+ last_position = position;
+
+ delay(500);
+
+ r = cubeb_stream_get_position(stream, &position);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_EQ(position, last_position);
+
+ cubeb_stream_destroy(stream);
+ cubeb_destroy(ctx);
+}
+
+static std::atomic<int> do_drain;
+static std::atomic<int> got_drain;
+
+static long
+test_drain_data_callback(cubeb_stream * stm, void * user_ptr, const void * /*inputbuffer*/, void * outputbuffer, long nframes)
+{
+ EXPECT_TRUE(stm && user_ptr == &dummy && outputbuffer && nframes > 0);
+ assert(outputbuffer);
+ if (do_drain == 1) {
+ do_drain = 2;
+ return 0;
+ }
+ /* once drain has started, callback must never be called again */
+ EXPECT_TRUE(do_drain != 2);
+ memset(outputbuffer, 0, nframes * sizeof(short));
+ 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_TRUE(!got_drain);
+ got_drain = 1;
+ }
+}
+
+TEST(cubeb, drain)
+{
+ int r;
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params params;
+ uint64_t position;
+
+ delay_callback = 0;
+ total_frames_written = 0;
+
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
+
+ params.format = STREAM_FORMAT;
+ params.rate = STREAM_RATE;
+ params.channels = STREAM_CHANNELS;
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
+ test_drain_data_callback, test_drain_state_callback, &dummy);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream, nullptr);
+
+ r = cubeb_stream_start(stream);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ delay(5000);
+
+ do_drain = 1;
+
+ for (;;) {
+ r = cubeb_stream_get_position(stream, &position);
+ ASSERT_EQ(r, CUBEB_OK);
+ if (got_drain) {
+ break;
+ } else {
+ ASSERT_LE(position, total_frames_written.load());
+ }
+ delay(500);
+ }
+
+ r = cubeb_stream_get_position(stream, &position);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_TRUE(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_LE(position, total_frames_written.load());
+
+ cubeb_stream_destroy(stream);
+ cubeb_destroy(ctx);
+
+ got_drain = 0;
+ do_drain = 0;
+}
+
+TEST(cubeb, DISABLED_eos_during_prefill)
+{
+ // This test needs to be implemented.
+}
+
+TEST(cubeb, DISABLED_stream_destroy_pending_drain)
+{
+ // This test needs to be implemented.
+}
+
+TEST(cubeb, stable_devid)
+{
+ /* Test that the devid field of cubeb_device_info is stable
+ * (ie. compares equal) over two invocations of
+ * cubeb_enumerate_devices(). */
+
+ int r;
+ cubeb * ctx;
+ cubeb_device_collection first;
+ cubeb_device_collection second;
+ cubeb_device_type all_devices =
+ (cubeb_device_type) (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT);
+ size_t n;
+
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
+
+ r = cubeb_enumerate_devices(ctx, all_devices, &first);
+ if (r == CUBEB_ERROR_NOT_SUPPORTED)
+ return;
+
+ ASSERT_EQ(r, CUBEB_OK);
+
+ r = cubeb_enumerate_devices(ctx, all_devices, &second);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ ASSERT_EQ(first.count, second.count);
+ for (n = 0; n < first.count; n++) {
+ ASSERT_EQ(first.device[n].devid, second.device[n].devid);
+ }
+
+ r = cubeb_device_collection_destroy(ctx, &first);
+ ASSERT_EQ(r, CUBEB_OK);
+ r = cubeb_device_collection_destroy(ctx, &second);
+ ASSERT_EQ(r, CUBEB_OK);
+ cubeb_destroy(ctx);
+}
+
+#undef STREAM_RATE
+#undef STREAM_LATENCY
+#undef STREAM_CHANNELS
+#undef STREAM_LAYOUT
+#undef STREAM_FORMAT
diff --git a/media/libcubeb/test/test_tone.cpp b/media/libcubeb/test/test_tone.cpp
new file mode 100644
index 0000000000..9d0e1afd32
--- /dev/null
+++ b/media/libcubeb/test/test_tone.cpp
@@ -0,0 +1,124 @@
+/*
+ * 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. */
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <memory>
+#include <limits.h>
+#include "cubeb/cubeb.h"
+#include <atomic>
+
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+
+#define SAMPLE_FREQUENCY 48000
+#define STREAM_FORMAT CUBEB_SAMPLE_S16LE
+
+/* store the phase of the generated waveform */
+struct cb_user_data {
+ std::atomic<long> position;
+};
+
+long data_cb_tone(cubeb_stream *stream, void *user, const void* /*inputbuffer*/, void *outputbuffer, long nframes)
+{
+ struct cb_user_data *u = (struct cb_user_data *)user;
+ short *b = (short *)outputbuffer;
+ 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);
+ b[i] = (SHRT_MAX / 2) * t1;
+ b[i] += (SHRT_MAX / 2) * t2;
+ /* European dial tone */
+ /*
+ t1 = sin(2*M_PI*(i + u->position)*425/SAMPLE_FREQUENCY);
+ b[i] = SHRT_MAX * t1;
+ */
+ }
+ /* remember our phase to avoid clicking on buffer transitions */
+ /* we'll still click if position overflows */
+ u->position += nframes;
+
+ return nframes;
+}
+
+void state_cb_tone(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:
+ fprintf(stderr, "stream started\n"); break;
+ case CUBEB_STATE_STOPPED:
+ fprintf(stderr, "stream stopped\n"); break;
+ case CUBEB_STATE_DRAINED:
+ fprintf(stderr, "stream drained\n"); break;
+ default:
+ fprintf(stderr, "unknown stream state %d\n", state);
+ }
+
+ return;
+}
+
+TEST(cubeb, tone)
+{
+ cubeb *ctx;
+ cubeb_stream *stream;
+ cubeb_stream_params params;
+ int r;
+
+ r = common_init(&ctx, "Cubeb tone example");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
+ cleanup_cubeb_at_exit(ctx, cubeb_destroy);
+
+ params.format = STREAM_FORMAT;
+ params.rate = SAMPLE_FREQUENCY;
+ params.channels = 1;
+ params.layout = CUBEB_LAYOUT_MONO;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ std::unique_ptr<cb_user_data> user_data(new cb_user_data());
+ ASSERT_TRUE(!!user_data) << "Error allocating user data";
+
+ user_data->position = 0;
+
+ r = cubeb_stream_init(ctx, &stream, "Cubeb tone (mono)", NULL, NULL, NULL, &params,
+ 4096, data_cb_tone, state_cb_tone, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(stream);
+ delay(5000);
+ cubeb_stream_stop(stream);
+
+ ASSERT_TRUE(user_data->position.load());
+}
+
+#undef SAMPLE_FREQUENCY
+#undef STREAM_FORMAT
diff --git a/media/libcubeb/test/test_triple_buffer.cpp b/media/libcubeb/test/test_triple_buffer.cpp
new file mode 100644
index 0000000000..a6e0049b79
--- /dev/null
+++ b/media/libcubeb/test/test_triple_buffer.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright © 2022 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+/* cubeb_triple_buffer test */
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include "cubeb/cubeb.h"
+#include "cubeb_triple_buffer.h"
+#include <atomic>
+#include <math.h>
+#include <memory>
+#include <stdio.h>
+#include <stdlib.h>
+#include <thread>
+
+#include "common.h"
+
+TEST(cubeb, triple_buffer)
+{
+ struct AB {
+ uint64_t a;
+ uint64_t b;
+ };
+ triple_buffer<AB> buffer;
+
+ std::atomic<bool> finished = {false};
+
+ ASSERT_TRUE(!buffer.updated());
+
+ auto t = std::thread([&finished, &buffer] {
+ AB ab;
+ ab.a = 0;
+ ab.b = UINT64_MAX;
+ uint64_t counter = 0;
+ do {
+ buffer.write(ab);
+ ab.a++;
+ ab.b--;
+ } while (counter++ < 1e6 && ab.a <= UINT64_MAX && ab.b != 0);
+ finished.store(true);
+ });
+
+ AB ab;
+ AB old_ab;
+ old_ab.a = 0;
+ old_ab.b = UINT64_MAX;
+
+ // Wait to have at least one value produced.
+ while (!buffer.updated()) {
+ }
+
+ // Check that the values are increasing (resp. descreasing) monotonically.
+ while (!finished) {
+ ab = buffer.read();
+ ASSERT_GE(ab.a, old_ab.a);
+ ASSERT_LE(ab.b, old_ab.b);
+ old_ab = ab;
+ }
+
+ t.join();
+}
diff --git a/media/libcubeb/test/test_utils.cpp b/media/libcubeb/test/test_utils.cpp
new file mode 100644
index 0000000000..cbdb960984
--- /dev/null
+++ b/media/libcubeb/test/test_utils.cpp
@@ -0,0 +1,72 @@
+#include "gtest/gtest.h"
+#include "cubeb_utils.h"
+
+TEST(cubeb, auto_array)
+{
+ auto_array<uint32_t> array;
+ auto_array<uint32_t> array2(10);
+ uint32_t a[10];
+
+ ASSERT_EQ(array2.length(), 0u);
+ ASSERT_EQ(array2.capacity(), 10u);
+
+
+ for (uint32_t i = 0; i < 10; i++) {
+ a[i] = i;
+ }
+
+ ASSERT_EQ(array.capacity(), 0u);
+ ASSERT_EQ(array.length(), 0u);
+
+ array.push(a, 10);
+
+ ASSERT_TRUE(!array.reserve(9));
+
+ for (uint32_t i = 0; i < 10; i++) {
+ ASSERT_EQ(array.data()[i], i);
+ }
+
+ ASSERT_EQ(array.capacity(), 10u);
+ ASSERT_EQ(array.length(), 10u);
+
+ uint32_t b[10];
+
+ array.pop(b, 5);
+
+ ASSERT_EQ(array.capacity(), 10u);
+ ASSERT_EQ(array.length(), 5u);
+ for (uint32_t i = 0; i < 5; i++) {
+ ASSERT_EQ(b[i], i);
+ ASSERT_EQ(array.data()[i], 5 + i);
+ }
+ uint32_t* bb = b + 5;
+ array.pop(bb, 5);
+
+ ASSERT_EQ(array.capacity(), 10u);
+ ASSERT_EQ(array.length(), 0u);
+ for (uint32_t i = 0; i < 5; i++) {
+ ASSERT_EQ(bb[i], 5 + i);
+ }
+
+ ASSERT_TRUE(!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_EQ(array.data()[10 * j + i], i);
+ }
+ }
+ ASSERT_EQ(array.length(), 20u);
+ ASSERT_EQ(array.capacity(), 20u);
+ array.pop(nullptr, 5);
+
+ for (uint32_t i = 0; i < 5; i++) {
+ ASSERT_EQ(array.data()[i], 5 + i);
+ }
+
+ ASSERT_EQ(array.length(), 15u);
+ ASSERT_EQ(array.capacity(), 20u);
+}
+