summaryrefslogtreecommitdiffstats
path: root/media/libcubeb/test
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /media/libcubeb/test
parentInitial commit. (diff)
downloadfirefox-upstream/124.0.1.tar.xz
firefox-upstream/124.0.1.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'media/libcubeb/test')
-rw-r--r--media/libcubeb/test/README.md13
-rw-r--r--media/libcubeb/test/common.h180
-rw-r--r--media/libcubeb/test/test_audio.cpp253
-rw-r--r--media/libcubeb/test/test_callback_ret.cpp254
-rw-r--r--media/libcubeb/test/test_device_changed_callback.cpp128
-rw-r--r--media/libcubeb/test/test_devices.cpp264
-rw-r--r--media/libcubeb/test/test_duplex.cpp380
-rw-r--r--media/libcubeb/test/test_latency.cpp43
-rw-r--r--media/libcubeb/test/test_logging.cpp196
-rw-r--r--media/libcubeb/test/test_loopback.cpp679
-rw-r--r--media/libcubeb/test/test_overload_callback.cpp108
-rw-r--r--media/libcubeb/test/test_record.cpp126
-rw-r--r--media/libcubeb/test/test_resampler.cpp1164
-rw-r--r--media/libcubeb/test/test_ring_array.cpp72
-rw-r--r--media/libcubeb/test/test_ring_buffer.cpp229
-rw-r--r--media/libcubeb/test/test_sanity.cpp721
-rw-r--r--media/libcubeb/test/test_tone.cpp130
-rw-r--r--media/libcubeb/test/test_triple_buffer.cpp67
-rw-r--r--media/libcubeb/test/test_utils.cpp70
19 files changed, 5077 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..6b06f9fcf0
--- /dev/null
+++ b/media/libcubeb/test/common.h
@@ -0,0 +1,180 @@
+/*
+ * 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 "cubeb/cubeb.h"
+#include "cubeb_mixer.h"
+#include "gtest/gtest.h"
+#include <cstdarg>
+#include <cstdio>
+#include <cstring>
+
+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..7c99a1814a
--- /dev/null
+++ b/media/libcubeb/test/test_audio.cpp
@@ -0,0 +1,253 @@
+/*
+ * 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 "cubeb/cubeb.h"
+#include <math.h>
+#include <memory>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.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..0737b60ae8
--- /dev/null
+++ b/media/libcubeb/test/test_callback_ret.cpp
@@ -0,0 +1,254 @@
+/*
+ * 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 "cubeb/cubeb.h"
+#include <atomic>
+#include <memory>
+#include <string>
+
+// #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);
+ if (r != CUBEB_OK) {
+ // not fatal
+ latency_frames = 1024;
+ }
+
+ 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_device_changed_callback.cpp b/media/libcubeb/test/test_device_changed_callback.cpp
new file mode 100644
index 0000000000..ddf810da65
--- /dev/null
+++ b/media/libcubeb/test/test_device_changed_callback.cpp
@@ -0,0 +1,128 @@
+/*
+ * 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 "cubeb/cubeb.h"
+#include <memory>
+#include <stdio.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..d0eab2da67
--- /dev/null
+++ b/media/libcubeb/test/test_devices.cpp
@@ -0,0 +1,264 @@
+/*
+ * 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 "cubeb/cubeb.h"
+#include "gtest/gtest.h"
+#include <memory>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+// #define ENABLE_NORMAL_LOG
+// #define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+static long
+data_cb_duplex(cubeb_stream * stream, void * user, const void * inputbuffer,
+ void * outputbuffer, long nframes)
+{
+ // noop, unused
+ return 0;
+}
+
+static 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..6a7ca98b1f
--- /dev/null
+++ b/media/libcubeb/test/test_duplex.cpp
@@ -0,0 +1,380 @@
+/*
+ * 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 "cubeb/cubeb.h"
+#include <atomic>
+#include <math.h>
+#include <memory>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "mozilla/gtest/MozHelpers.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
+
+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;
+ }
+ 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;
+
+ mozilla::gtest::DisableCrashReporter();
+
+ 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..97f24ea498
--- /dev/null
+++ b/media/libcubeb/test/test_latency.cpp
@@ -0,0 +1,43 @@
+#include "cubeb/cubeb.h"
+#include "gtest/gtest.h"
+#include <memory>
+#include <stdlib.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..54ff718152
--- /dev/null
+++ b/media/libcubeb/test/test_logging.cpp
@@ -0,0 +1,196 @@
+/*
+ * 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};
+
+static 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
+}
+
+static long
+data_cb_load(cubeb_stream * stream, void * user, const void * inputbuffer,
+ void * outputbuffer, long nframes)
+{
+ data_callback_call_count++;
+ return nframes;
+}
+
+static 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);
+ if (r != CUBEB_OK) {
+ // not fatal
+ latency_frames = 1024;
+ }
+
+ 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..7410bedb8b
--- /dev/null
+++ b/media/libcubeb/test/test_loopback.cpp
@@ -0,0 +1,679 @@
+/*
+ * 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 "cubeb/cubeb.h"
+#include <algorithm>
+#include <math.h>
+#include <memory>
+#include <mutex>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string>
+// #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..14c1ec3e65
--- /dev/null
+++ b/media/libcubeb/test/test_overload_callback.cpp
@@ -0,0 +1,108 @@
+/*
+ * 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 "cubeb/cubeb.h"
+#include <atomic>
+#include <math.h>
+#include <memory>
+#include <stdio.h>
+#include <stdlib.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};
+
+static 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;
+}
+
+static 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..5b46b21020
--- /dev/null
+++ b/media/libcubeb/test/test_record.cpp
@@ -0,0 +1,126 @@
+/*
+ * 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 "cubeb/cubeb.h"
+#include <atomic>
+#include <math.h>
+#include <memory>
+#include <stdio.h>
+#include <stdlib.h>
+
+// #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..adbcfd8c5e
--- /dev/null
+++ b/media/libcubeb/test/test_resampler.cpp
@@ -0,0 +1,1164 @@
+/*
+ * 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 "common.h"
+#include "cubeb_resampler_internal.h"
+#include "gtest/gtest.h"
+#include <algorithm>
+#include <iostream>
+#include <stdio.h>
+
+/* 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..a364684c77
--- /dev/null
+++ b/media/libcubeb/test/test_ring_array.cpp
@@ -0,0 +1,72 @@
+#include "gtest/gtest.h"
+#ifdef __APPLE__
+#include "cubeb/cubeb.h"
+#include "cubeb_ring_array.h"
+#include <CoreAudio/CoreAudioTypes.h>
+#include <iostream>
+#include <string.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..fffe1087e4
--- /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.
+ */
+
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif
+
+#include "cubeb_ringbuffer.h"
+#include "gtest/gtest.h"
+#include <chrono>
+#include <iostream>
+#include <thread>
+
+/* 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..17d3c542b4
--- /dev/null
+++ b/media/libcubeb/test/test_sanity.cpp
@@ -0,0 +1,721 @@
+/*
+ * 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 <math.h>
+#include <stdio.h>
+#include <string.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);
+ // The OpenSL backend performs client-side interpolation for its position and
+ // its drain implementation isn't very accurate.
+ if (strcmp(cubeb_get_backend_id(ctx), "opensl")) {
+ 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..56ff8767ba
--- /dev/null
+++ b/media/libcubeb/test/test_tone.cpp
@@ -0,0 +1,130 @@
+/*
+ * 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 "cubeb/cubeb.h"
+#include <atomic>
+#include <limits.h>
+#include <math.h>
+#include <memory>
+#include <stdio.h>
+#include <stdlib.h>
+
+// #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..a6227d4d7a
--- /dev/null
+++ b/media/libcubeb/test/test_utils.cpp
@@ -0,0 +1,70 @@
+#include "cubeb_utils.h"
+#include "gtest/gtest.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);
+}