early-access version 1255

This commit is contained in:
pineappleEA
2020-12-28 15:15:37 +00:00
parent 84b39492d1
commit 78b48028e1
6254 changed files with 1868140 additions and 0 deletions

13
externals/cubeb/test/README.md vendored Executable file
View File

@@ -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.

145
externals/cubeb/test/common.h vendored Executable file
View File

@@ -0,0 +1,145 @@
/*
* Copyright © 2013 Sebastien Alaiwan
*
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
*/
#if !defined(TEST_COMMON)
#define TEST_COMMON
#if defined( _WIN32)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <objbase.h>
#include <windows.h>
#else
#include <unistd.h>
#endif
#include <cstdarg>
#include "cubeb/cubeb.h"
#include "cubeb_mixer.h"
template<typename T, size_t N>
constexpr size_t
ARRAY_LENGTH(T(&)[N])
{
return N;
}
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;
int has_available_input_device(cubeb * ctx)
{
cubeb_device_collection devices;
int input_device_available = 0;
int r;
/* Bail out early if the host does not have input devices. */
r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_INPUT, &devices);
if (r != CUBEB_OK) {
fprintf(stderr, "error enumerating devices.");
return 0;
}
if (devices.count == 0) {
fprintf(stderr, "no input device available, skipping test.\n");
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);
return !!input_device_available;
}
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. */
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 */

244
externals/cubeb/test/test_audio.cpp vendored Executable file
View File

@@ -0,0 +1,244 @@
/*
* Copyright © 2013 Sebastien Alaiwan <sebastien.alaiwan@gmail.com>
*
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
*/
/* libcubeb api/function exhaustive test. Plays a series of tones in different
* conditions. */
#include "gtest/gtest.h"
#if !defined(_XOPEN_SOURCE)
#define _XOPEN_SOURCE 600
#endif
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <memory>
#include <string.h>
#include "cubeb/cubeb.h"
#include <string>
//#define ENABLE_NORMAL_LOG
//#define ENABLE_VERBOSE_LOG
#include "common.h"
using namespace std;
#define MAX_NUM_CHANNELS 32
#if !defined(M_PI)
#define M_PI 3.14159265358979323846
#endif
#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);
}
}
}

207
externals/cubeb/test/test_callback_ret.cpp vendored Executable file
View File

@@ -0,0 +1,207 @@
/*
* Copyright <20> 2017 Mozilla Foundation
*
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
*/
/* libcubeb api/function test. Test that different return values from user
specified callbacks are handled correctly. */
#include "gtest/gtest.h"
#if !defined(_XOPEN_SOURCE)
#define _XOPEN_SOURCE 600
#endif
#include <memory>
#include <atomic>
#include <string>
#include "cubeb/cubeb.h"
//#define ENABLE_NORMAL_LOG
//#define ENABLE_VERBOSE_LOG
#include "common.h"
const uint32_t SAMPLE_FREQUENCY = 48000;
const cubeb_sample_format SAMPLE_FORMAT = CUBEB_SAMPLE_S16NE;
enum test_direction {
INPUT_ONLY,
OUTPUT_ONLY,
DUPLEX
};
// Structure which is used by data callbacks to track the total callbacks
// executed vs the number of callbacks expected.
struct user_state_callback_ret {
std::atomic<int> cb_count{ 0 };
std::atomic<int> expected_cb_count{ 0 };
};
// 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;
}
void state_cb_ret(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_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) &&
!has_available_input_device(ctx)) {
/* This test needs an available input device, skip it if this host does not
* have one. */
return;
}
// Setup all params, but only pass them later as required by direction
input_params.format = SAMPLE_FORMAT;
input_params.rate = SAMPLE_FREQUENCY;
input_params.channels = 1;
input_params.layout = CUBEB_LAYOUT_MONO;
input_params.prefs = CUBEB_STREAM_PREF_NONE;
output_params = input_params;
r = cubeb_get_min_latency(ctx, &input_params, &latency_frames);
ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
switch (direction)
{
case INPUT_ONLY:
r = cubeb_stream_init(ctx, &stream, "Cubeb callback ret input",
NULL, &input_params, NULL, NULL,
latency_frames, data_cb, state_cb_ret, &user_state);
break;
case OUTPUT_ONLY:
r = cubeb_stream_init(ctx, &stream, "Cubeb callback ret output",
NULL, NULL, NULL, &output_params,
latency_frames, data_cb, state_cb_ret, &user_state);
break;
case DUPLEX:
r = cubeb_stream_init(ctx, &stream, "Cubeb callback ret duplex",
NULL, &input_params, NULL, &output_params,
latency_frames, data_cb, state_cb_ret, &user_state);
break;
default:
ASSERT_TRUE(false) << "Unrecognized test direction!";
}
EXPECT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
cleanup_stream_at_exit(stream, cubeb_stream_destroy);
cubeb_stream_start(stream);
delay(100);
cubeb_stream_stop(stream);
ASSERT_EQ(user_state.expected_cb_count, user_state.cb_count) <<
"Callback called unexpected number of times for " << test_desc << "!";
}
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");
}
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");
}
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");
}

260
externals/cubeb/test/test_deadlock.cpp vendored Executable file
View File

@@ -0,0 +1,260 @@
/*
* Copyright © 2017 Mozilla Foundation
*
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
*
*
* Purpose
* =============================================================================
* In CoreAudio, the data callback will holds a mutex shared with AudioUnit
* (mutex_AU). Thus, if the callback request another mutex M held by the another
* function, without releasing mutex_AU, then it will cause a deadlock when the
* another function, which holds the mutex M, request to use AudioUnit.
*
* The following figure illustrates the deadlock in bug 1337805:
* https://bugzilla.mozilla.org/show_bug.cgi?id=1337805
* (The detail analysis can be found on bug 1350511:
* https://bugzilla.mozilla.org/show_bug.cgi?id=1350511)
*
* holds
* data_callback <---------- mutext_AudioUnit(mutex_AU)
* | ^
* | |
* | request | request
* | |
* v holds |
* mutex_cubeb ------------> get_channel_layout
*
* In this example, the "audiounit_get_channel_layout" in f4edfb8:
* https://github.com/kinetiknz/cubeb/blob/f4edfb8eea920887713325e44773f3a2d959860c/src/cubeb_audiounit.cpp#L2725
* requests the mutex_AU to create an AudioUnit, when it holds a mutex for cubeb
* context. Meanwhile, the data callback who holds the mutex_AU requests the
* mutex for cubeb context. As a result, it causes a deadlock.
*
* The problem is solve by pull 236: https://github.com/kinetiknz/cubeb/pull/236
* We store the latest channel layout and return it when there is an active
* AudioUnit, otherwise, we will create an AudioUnit to get it.
*
* Although the problem is solved, to prevent it happens again, we add the test
* here in case someone without such knowledge misuses the AudioUnit in
* get_channel_layout. Moreover, it's a good way to record the known issues
* to warn other developers.
*/
#include "gtest/gtest.h"
//#define ENABLE_NORMAL_LOG
//#define ENABLE_VERBOSE_LOG
#include "common.h" // for layout_infos
#include "cubeb/cubeb.h" // for cubeb utils
#include "cubeb_utils.h" // for owned_critical_section, auto_lock
#include <iostream> // for fprintf
#include <pthread.h> // for pthread
#include <signal.h> // for signal
#include <stdexcept> // for std::logic_error
#include <string> // for std::string
#include <unistd.h> // for sleep, usleep
#include <atomic> // for std::atomic
// The signal alias for calling our thread killer.
#define CALL_THREAD_KILLER SIGUSR1
// This indicator will become true when our pending task thread is killed by
// ourselves.
bool killed = false;
// This indicator will become true when the assigned task is done.
std::atomic<bool> task_done{ false };
// Indicating the data callback is fired or not.
bool called = false;
// Toggle to true when running data callback. Before data callback gets
// the mutex for cubeb context, it toggles back to false.
// The task to get channel layout should be executed when this is true.
std::atomic<bool> callbacking_before_getting_context{ false };
owned_critical_section context_mutex;
cubeb * context = nullptr;
cubeb * get_cubeb_context_unlocked()
{
if (context) {
return context;
}
int r = CUBEB_OK;
r = common_init(&context, "Cubeb deadlock test");
if (r != CUBEB_OK) {
context = nullptr;
}
return context;
}
cubeb * get_cubeb_context()
{
auto_lock lock(context_mutex);
return get_cubeb_context_unlocked();
}
void state_cb_audio(cubeb_stream * /*stream*/, void * /*user*/, cubeb_state /*state*/)
{
}
// Fired by coreaudio's rendering mechanism. It holds a mutex shared with the
// current used AudioUnit.
template<typename T>
long data_cb(cubeb_stream * /*stream*/, void * /*user*/,
const void * /*inputbuffer*/, void * outputbuffer, long nframes)
{
called = true;
uint64_t tid; // Current thread id.
pthread_threadid_np(NULL, &tid);
fprintf(stderr, "Audio output is on thread %llu\n", tid);
if (!task_done) {
callbacking_before_getting_context = true;
fprintf(stderr, "[%llu] time to switch thread\n", tid);
// Force to switch threads by sleeping 10 ms. Notice that anything over
// 10ms would create a glitch. It's intended here for test, so the delay
// is ok.
usleep(10000);
callbacking_before_getting_context = false;
}
fprintf(stderr, "[%llu] try getting backend id ...\n", tid);
// Try requesting mutex for context by get_cubeb_context()
// when holding a mutex for AudioUnit.
char const * backend_id = cubeb_get_backend_id(get_cubeb_context());
fprintf(stderr, "[%llu] callback on %s\n", tid, backend_id);
// Mute the output (or get deaf)
memset(outputbuffer, 0, nframes * 2 * sizeof(float));
return nframes;
}
// Called by wait_to_get_layout, which is run out of main thread.
void get_preferred_channel_layout()
{
auto_lock lock(context_mutex);
cubeb * context = get_cubeb_context_unlocked();
ASSERT_TRUE(!!context);
// We will cause a deadlock if cubeb_get_preferred_channel_layout requests
// mutex for AudioUnit when it holds mutex for context.
cubeb_channel_layout layout;
int r = cubeb_get_preferred_channel_layout(context, &layout);
ASSERT_EQ(r == CUBEB_OK, layout != CUBEB_LAYOUT_UNDEFINED);
fprintf(stderr, "layout is %s\n", layout_infos[layout].name);
}
void * wait_to_get_layout(void *)
{
uint64_t tid; // Current thread id.
pthread_threadid_np(NULL, &tid);
while(!callbacking_before_getting_context) {
fprintf(stderr, "[%llu] waiting for data callback ...\n", tid);
usleep(1000); // Force to switch threads by sleeping 1 ms.
}
fprintf(stderr, "[%llu] try getting channel layout ...\n", tid);
get_preferred_channel_layout(); // Deadlock checkpoint.
task_done = true;
return NULL;
}
void * watchdog(void * s)
{
uint64_t tid; // Current thread id.
pthread_threadid_np(NULL, &tid);
pthread_t subject = *((pthread_t *) s);
uint64_t stid; // task thread id.
pthread_threadid_np(subject, &stid);
unsigned int sec = 2;
fprintf(stderr, "[%llu] sleep %d seconds before checking task for thread %llu\n", tid, sec, stid);
sleep(sec); // Force to switch threads.
fprintf(stderr, "[%llu] check task for thread %llu now\n", tid, stid);
if (!task_done) {
fprintf(stderr, "[%llu] kill the task thread %llu\n", tid, stid);
pthread_kill(subject, CALL_THREAD_KILLER);
pthread_detach(subject);
// pthread_kill doesn't release the mutex held by the killed thread,
// so we need to unlock it manually.
context_mutex.unlock();
}
fprintf(stderr, "[%llu] the assigned task for thread %llu is %sdone\n", tid, stid, (task_done) ? "" : "not ");
return NULL;
}
void thread_killer(int signal)
{
ASSERT_EQ(signal, CALL_THREAD_KILLER);
fprintf(stderr, "task thread is killed!\n");
killed = true;
}
TEST(cubeb, run_deadlock_test)
{
#if !defined(__APPLE__)
FAIL() << "Deadlock test is only for OSX now";
#endif
cubeb * ctx = get_cubeb_context();
ASSERT_TRUE(!!ctx);
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
cubeb_stream_params params;
params.format = CUBEB_SAMPLE_FLOAT32NE;
params.rate = 44100;
params.channels = 2;
params.layout = CUBEB_LAYOUT_STEREO;
params.prefs = CUBEB_STREAM_PREF_NONE;
cubeb_stream * stream = NULL;
int r = cubeb_stream_init(ctx, &stream, "test deadlock", NULL, NULL, NULL,
&params, 512, &data_cb<float>, state_cb_audio, NULL);
ASSERT_EQ(r, CUBEB_OK);
std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
cleanup_stream_at_exit(stream, cubeb_stream_destroy);
// Install signal handler.
signal(CALL_THREAD_KILLER, thread_killer);
pthread_t subject, detector;
pthread_create(&subject, NULL, wait_to_get_layout, NULL);
pthread_create(&detector, NULL, watchdog, (void *) &subject);
uint64_t stid, dtid;
pthread_threadid_np(subject, &stid);
pthread_threadid_np(detector, &dtid);
fprintf(stderr, "task thread %llu, monitor thread %llu are created\n", stid, dtid);
cubeb_stream_start(stream);
pthread_join(subject, NULL);
pthread_join(detector, NULL);
ASSERT_TRUE(called);
fprintf(stderr, "\n%sDeadlock detected!\n", (called && !task_done.load()) ? "" : "No ");
// Check the task is killed by ourselves if deadlock happends.
// Otherwise, thread_killer should not be triggered.
ASSERT_NE(task_done.load(), killed);
ASSERT_TRUE(task_done.load());
cubeb_stream_stop(stream);
}

View File

@@ -0,0 +1,109 @@
/*
* Copyright © 2018 Mozilla Foundation
*
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
*/
/* libcubeb api/function test. Check behaviors of registering device changed
* callbacks for the streams. */
#include "gtest/gtest.h"
#if !defined(_XOPEN_SOURCE)
#define _XOPEN_SOURCE 600
#endif
#include <stdio.h>
#include <memory>
#include "cubeb/cubeb.h"
//#define ENABLE_NORMAL_LOG
//#define ENABLE_VERBOSE_LOG
#include "common.h"
#define SAMPLE_FREQUENCY 48000
#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
#define INPUT_CHANNELS 1
#define INPUT_LAYOUT CUBEB_LAYOUT_MONO
#define OUTPUT_CHANNELS 2
#define OUTPUT_LAYOUT CUBEB_LAYOUT_STEREO
long data_callback(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
{
return 0;
}
void state_callback(cubeb_stream * stream, void * user, cubeb_state state)
{
}
void device_changed_callback(void * user)
{
fprintf(stderr, "device changed callback\n");
ASSERT_TRUE(false) << "Error: device changed callback"
" called without changing devices";
}
void test_registering_null_callback_twice(cubeb_stream * stream)
{
int r = cubeb_stream_register_device_changed_callback(stream, nullptr);
if (r == CUBEB_ERROR_NOT_SUPPORTED) {
return;
}
ASSERT_EQ(r, CUBEB_OK) << "Error registering null device changed callback";
r = cubeb_stream_register_device_changed_callback(stream, nullptr);
ASSERT_EQ(r, CUBEB_OK) << "Error registering null device changed callback again";
}
void test_registering_and_unregistering_callback(cubeb_stream * stream)
{
int r = cubeb_stream_register_device_changed_callback(stream, device_changed_callback);
if (r == CUBEB_ERROR_NOT_SUPPORTED) {
return;
}
ASSERT_EQ(r, CUBEB_OK) << "Error registering device changed callback";
r = cubeb_stream_register_device_changed_callback(stream, nullptr);
ASSERT_EQ(r, CUBEB_OK) << "Error unregistering device changed callback";
}
TEST(cubeb, device_changed_callbacks)
{
cubeb * ctx;
cubeb_stream * stream;
cubeb_stream_params input_params;
cubeb_stream_params output_params;
int r = CUBEB_OK;
uint32_t latency_frames = 0;
r = common_init(&ctx, "Cubeb duplex example with device change");
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
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);
}

255
externals/cubeb/test/test_devices.cpp vendored Executable file
View File

@@ -0,0 +1,255 @@
/*
* Copyright © 2015 Haakon Sporsheim <haakon.sporsheim@telenordigital.com>
*
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
*/
/* libcubeb enumerate device test/example.
* Prints out a list of devices enumerated. */
#include "gtest/gtest.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory>
#include "cubeb/cubeb.h"
//#define ENABLE_NORMAL_LOG
//#define ENABLE_VERBOSE_LOG
#include "common.h"
long data_cb_duplex(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
{
// noop, unused
return 0;
}
void state_cb_duplex(cubeb_stream * stream, void * /*user*/, cubeb_state state)
{
// noop, unused
}
static void
print_device_info(cubeb_device_info * info, FILE * f)
{
char devfmts[64] = "";
const char * devtype, * devstate, * devdeffmt;
switch (info->type) {
case CUBEB_DEVICE_TYPE_INPUT:
devtype = "input";
break;
case CUBEB_DEVICE_TYPE_OUTPUT:
devtype = "output";
break;
case CUBEB_DEVICE_TYPE_UNKNOWN:
default:
devtype = "unknown?";
break;
};
switch (info->state) {
case CUBEB_DEVICE_STATE_DISABLED:
devstate = "disabled";
break;
case CUBEB_DEVICE_STATE_UNPLUGGED:
devstate = "unplugged";
break;
case CUBEB_DEVICE_STATE_ENABLED:
devstate = "enabled";
break;
default:
devstate = "unknown?";
break;
};
switch (info->default_format) {
case CUBEB_DEVICE_FMT_S16LE:
devdeffmt = "S16LE";
break;
case CUBEB_DEVICE_FMT_S16BE:
devdeffmt = "S16BE";
break;
case CUBEB_DEVICE_FMT_F32LE:
devdeffmt = "F32LE";
break;
case CUBEB_DEVICE_FMT_F32BE:
devdeffmt = "F32BE";
break;
default:
devdeffmt = "unknown?";
break;
};
if (info->format & CUBEB_DEVICE_FMT_S16LE)
strcat(devfmts, " S16LE");
if (info->format & CUBEB_DEVICE_FMT_S16BE)
strcat(devfmts, " S16BE");
if (info->format & CUBEB_DEVICE_FMT_F32LE)
strcat(devfmts, " F32LE");
if (info->format & CUBEB_DEVICE_FMT_F32BE)
strcat(devfmts, " F32BE");
fprintf(f,
"dev: \"%s\"%s\n"
"\tName: \"%s\"\n"
"\tGroup: \"%s\"\n"
"\tVendor: \"%s\"\n"
"\tType: %s\n"
"\tState: %s\n"
"\tCh: %u\n"
"\tFormat: %s (0x%x) (default: %s)\n"
"\tRate: %u - %u (default: %u)\n"
"\tLatency: lo %u frames, hi %u frames\n",
info->device_id, info->preferred ? " (PREFERRED)" : "",
info->friendly_name, info->group_id, info->vendor_name,
devtype, devstate, info->max_channels,
(devfmts[0] == '\0') ? devfmts : devfmts + 1,
(unsigned int)info->format, devdeffmt,
info->min_rate, info->max_rate, info->default_rate,
info->latency_lo, info->latency_hi);
}
static void
print_device_collection(cubeb_device_collection * collection, FILE * f)
{
uint32_t i;
for (i = 0; i < collection->count; i++)
print_device_info(&collection->device[i], f);
}
TEST(cubeb, destroy_default_collection)
{
int r;
cubeb * ctx = NULL;
cubeb_device_collection collection{ nullptr, 0 };
r = common_init(&ctx, "Cubeb audio test");
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
ASSERT_EQ(collection.device, nullptr);
ASSERT_EQ(collection.count, (size_t) 0);
r = cubeb_device_collection_destroy(ctx, &collection);
if (r != CUBEB_ERROR_NOT_SUPPORTED) {
ASSERT_EQ(r, CUBEB_OK);
ASSERT_EQ(collection.device, nullptr);
ASSERT_EQ(collection.count, (size_t) 0);
}
}
TEST(cubeb, enumerate_devices)
{
int r;
cubeb * ctx = NULL;
cubeb_device_collection collection;
r = common_init(&ctx, "Cubeb audio test");
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
fprintf(stdout, "Enumerating input devices for backend %s\n",
cubeb_get_backend_id(ctx));
r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_INPUT, &collection);
if (r == CUBEB_ERROR_NOT_SUPPORTED) {
fprintf(stderr, "Device enumeration not supported"
" for this backend, skipping this test.\n");
return;
}
ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;
fprintf(stdout, "Found %zu input devices\n", collection.count);
print_device_collection(&collection, stdout);
cubeb_device_collection_destroy(ctx, &collection);
fprintf(stdout, "Enumerating output devices for backend %s\n",
cubeb_get_backend_id(ctx));
r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection);
ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;
fprintf(stdout, "Found %zu output devices\n", collection.count);
print_device_collection(&collection, stdout);
cubeb_device_collection_destroy(ctx, &collection);
uint32_t count_before_creating_duplex_stream;
r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection);
ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;
count_before_creating_duplex_stream = collection.count;
cubeb_device_collection_destroy(ctx, &collection);
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));
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";
}

181
externals/cubeb/test/test_duplex.cpp vendored Executable file
View File

@@ -0,0 +1,181 @@
/*
* Copyright © 2016 Mozilla Foundation
*
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
*/
/* libcubeb api/function test. Loops input back to output and check audio
* is flowing. */
#include "gtest/gtest.h"
#if !defined(_XOPEN_SOURCE)
#define _XOPEN_SOURCE 600
#endif
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <memory>
#include "cubeb/cubeb.h"
#include <atomic>
//#define ENABLE_NORMAL_LOG
//#define ENABLE_VERBOSE_LOG
#include "common.h"
#define SAMPLE_FREQUENCY 48000
#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
#define INPUT_CHANNELS 1
#define INPUT_LAYOUT CUBEB_LAYOUT_MONO
#define OUTPUT_CHANNELS 2
#define OUTPUT_LAYOUT CUBEB_LAYOUT_STEREO
struct user_state_duplex
{
std::atomic<int> invalid_audio_value{ 0 };
};
long data_cb_duplex(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
{
user_state_duplex * u = reinterpret_cast<user_state_duplex*>(user);
float *ib = (float *)inputbuffer;
float *ob = (float *)outputbuffer;
if (stream == NULL || inputbuffer == NULL || outputbuffer == NULL) {
return CUBEB_ERROR;
}
// Loop back: upmix the single input channel to the two output channels,
// checking if there is noise in the process.
long output_index = 0;
for (long i = 0; i < nframes; i++) {
if (ib[i] <= -1.0 || ib[i] >= 1.0) {
u->invalid_audio_value = 1;
break;
}
ob[output_index] = ob[output_index + 1] = ib[i];
output_index += 2;
}
return nframes;
}
void state_cb_duplex(cubeb_stream * stream, void * /*user*/, cubeb_state state)
{
if (stream == NULL)
return;
switch (state) {
case CUBEB_STATE_STARTED:
fprintf(stderr, "stream started\n"); break;
case CUBEB_STATE_STOPPED:
fprintf(stderr, "stream stopped\n"); break;
case CUBEB_STATE_DRAINED:
fprintf(stderr, "stream drained\n"); break;
default:
fprintf(stderr, "unknown stream state %d\n", state);
}
return;
}
TEST(cubeb, duplex)
{
cubeb *ctx;
cubeb_stream *stream;
cubeb_stream_params input_params;
cubeb_stream_params output_params;
int r;
user_state_duplex stream_state;
uint32_t latency_frames = 0;
r = common_init(&ctx, "Cubeb duplex example");
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
/* This test needs an available input device, skip it if this host does not
* have one. */
if (!has_available_input_device(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";
}
TEST(cubeb, duplex_collection_change)
{
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 duplex example with collection change");
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
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";
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_cb_duplex, state_cb_duplex, nullptr);
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
cubeb_stream_destroy(stream);
}

47
externals/cubeb/test/test_latency.cpp vendored Executable file
View File

@@ -0,0 +1,47 @@
#include "gtest/gtest.h"
#include <stdlib.h>
#include <memory>
#include "cubeb/cubeb.h"
//#define ENABLE_NORMAL_LOG
//#define ENABLE_VERBOSE_LOG
#include "common.h"
TEST(cubeb, latency)
{
cubeb * ctx = NULL;
int r;
uint32_t max_channels;
uint32_t preferred_rate;
uint32_t latency_frames;
r = common_init(&ctx, "Cubeb audio test");
ASSERT_EQ(r, CUBEB_OK);
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
r = cubeb_get_max_channel_count(ctx, &max_channels);
ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
if (r == CUBEB_OK) {
ASSERT_GT(max_channels, 0u);
}
r = cubeb_get_preferred_sample_rate(ctx, &preferred_rate);
ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
if (r == CUBEB_OK) {
ASSERT_GT(preferred_rate, 0u);
}
cubeb_stream_params params = {
CUBEB_SAMPLE_FLOAT32NE,
preferred_rate,
max_channels,
CUBEB_LAYOUT_UNDEFINED,
CUBEB_STREAM_PREF_NONE
};
r = cubeb_get_min_latency(ctx, &params, &latency_frames);
ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
if (r == CUBEB_OK) {
ASSERT_GT(latency_frames, 0u);
}
}

578
externals/cubeb/test/test_loopback.cpp vendored Executable file
View File

@@ -0,0 +1,578 @@
/*
* Copyright © 2017 Mozilla Foundation
*
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
*/
/* libcubeb api/function test. Requests a loopback device and checks that
output is being looped back to input. NOTE: Usage of output devices while
performing this test will cause flakey results! */
#include "gtest/gtest.h"
#if !defined(_XOPEN_SOURCE)
#define _XOPEN_SOURCE 600
#endif
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <algorithm>
#include <memory>
#include <mutex>
#include <string>
#include "cubeb/cubeb.h"
//#define ENABLE_NORMAL_LOG
//#define ENABLE_VERBOSE_LOG
#include "common.h"
const uint32_t SAMPLE_FREQUENCY = 48000;
const uint32_t TONE_FREQUENCY = 440;
const double OUTPUT_AMPLITUDE = 0.25;
const int32_t NUM_FRAMES_TO_OUTPUT = SAMPLE_FREQUENCY / 20; /* play ~50ms of samples */
template<typename T> T ConvertSampleToOutput(double input);
template<> float ConvertSampleToOutput(double input) { return float(input); }
template<> short ConvertSampleToOutput(double input) { return short(input * 32767.0f); }
template<typename T> double ConvertSampleFromOutput(T sample);
template<> double ConvertSampleFromOutput(float sample) { return double(sample); }
template<> double ConvertSampleFromOutput(short sample) { return double(sample / 32767.0); }
/* Simple cross correlation to help find phase shift. Not a performant impl */
std::vector<double> cross_correlate(std::vector<double> & f,
std::vector<double> & g,
size_t signal_length)
{
/* the length we sweep our window through to find the cross correlation */
size_t sweep_length = f.size() - signal_length + 1;
std::vector<double> correlation;
correlation.reserve(sweep_length);
for (size_t i = 0; i < sweep_length; i++) {
double accumulator = 0.0;
for (size_t j = 0; j < signal_length; j++) {
accumulator += f.at(j) * g.at(i + j);
}
correlation.push_back(accumulator);
}
return correlation;
}
/* best effort discovery of phase shift between output and (looped) input*/
size_t find_phase(std::vector<double> & output_frames,
std::vector<double> & input_frames,
size_t signal_length)
{
std::vector<double> correlation = cross_correlate(output_frames, input_frames, signal_length);
size_t phase = 0;
double max_correlation = correlation.at(0);
for (size_t i = 1; i < correlation.size(); i++) {
if (correlation.at(i) > max_correlation) {
max_correlation = correlation.at(i);
phase = i;
}
}
return phase;
}
std::vector<double> normalize_frames(std::vector<double> & frames) {
double max = abs(*std::max_element(frames.begin(), frames.end(),
[](double a, double b) { return abs(a) < abs(b); }));
std::vector<double> normalized_frames;
normalized_frames.reserve(frames.size());
for (const double frame : frames) {
normalized_frames.push_back(frame / max);
}
return normalized_frames;
}
/* heuristic comparison of aligned output and input signals, gets flaky if TONE_FREQUENCY is too high */
void compare_signals(std::vector<double> & output_frames,
std::vector<double> & input_frames)
{
ASSERT_EQ(output_frames.size(), input_frames.size()) << "#Output frames != #input frames";
size_t num_frames = output_frames.size();
std::vector<double> normalized_output_frames = normalize_frames(output_frames);
std::vector<double> normalized_input_frames = normalize_frames(input_frames);
/* calculate mean absolute errors */
/* mean absolute errors between output and input */
double io_mas = 0.0;
/* mean absolute errors between output and silence */
double output_silence_mas = 0.0;
/* mean absolute errors between input and silence */
double input_silence_mas = 0.0;
for (size_t i = 0; i < num_frames; i++) {
io_mas += abs(normalized_output_frames.at(i) - normalized_input_frames.at(i));
output_silence_mas += abs(normalized_output_frames.at(i));
input_silence_mas += abs(normalized_input_frames.at(i));
}
io_mas /= num_frames;
output_silence_mas /= num_frames;
input_silence_mas /= num_frames;
ASSERT_LT(io_mas, output_silence_mas)
<< "Error between output and input should be less than output and silence!";
ASSERT_LT(io_mas, input_silence_mas)
<< "Error between output and input should be less than output and silence!";
/* make sure extrema are in (roughly) correct location */
/* number of maxima + minama expected in the frames*/
const long NUM_EXTREMA = 2 * TONE_FREQUENCY * NUM_FRAMES_TO_OUTPUT / SAMPLE_FREQUENCY;
/* expected index of first maxima */
const long FIRST_MAXIMUM_INDEX = SAMPLE_FREQUENCY / TONE_FREQUENCY / 4;
/* Threshold we expect all maxima and minima to be above or below. Ideally
the extrema would be 1 or -1, but particularly at the start of loopback
the values seen can be significantly lower. */
const double THRESHOLD = 0.5;
for (size_t i = 0; i < NUM_EXTREMA; i++) {
bool is_maximum = i % 2 == 0;
/* expected offset to current extreme: i * stide between extrema */
size_t offset = i * SAMPLE_FREQUENCY / TONE_FREQUENCY / 2;
if (is_maximum) {
ASSERT_GT(normalized_output_frames.at(FIRST_MAXIMUM_INDEX + offset), THRESHOLD)
<< "Output frames have unexpected missing maximum!";
ASSERT_GT(normalized_input_frames.at(FIRST_MAXIMUM_INDEX + offset), THRESHOLD)
<< "Input frames have unexpected missing maximum!";
} else {
ASSERT_LT(normalized_output_frames.at(FIRST_MAXIMUM_INDEX + offset), -THRESHOLD)
<< "Output frames have unexpected missing minimum!";
ASSERT_LT(normalized_input_frames.at(FIRST_MAXIMUM_INDEX + offset), -THRESHOLD)
<< "Input frames have unexpected missing minimum!";
}
}
}
struct user_state_loopback {
std::mutex user_state_mutex;
long position = 0;
/* track output */
std::vector<double> output_frames;
/* track input */
std::vector<double> input_frames;
};
template<typename T>
long data_cb_loop_duplex(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
{
struct user_state_loopback * u = (struct user_state_loopback *) user;
T * ib = (T *) inputbuffer;
T * ob = (T *) outputbuffer;
if (stream == NULL || inputbuffer == NULL || outputbuffer == NULL) {
return CUBEB_ERROR;
}
std::lock_guard<std::mutex> lock(u->user_state_mutex);
/* generate our test tone on the fly */
for (int i = 0; i < nframes; i++) {
double tone = 0.0;
if (u->position + i < NUM_FRAMES_TO_OUTPUT) {
/* generate sine wave */
tone = sin(2 * M_PI*(i + u->position) * TONE_FREQUENCY / SAMPLE_FREQUENCY);
tone *= OUTPUT_AMPLITUDE;
}
ob[i] = ConvertSampleToOutput<T>(tone);
u->output_frames.push_back(tone);
/* store any looped back output, may be silence */
u->input_frames.push_back(ConvertSampleFromOutput(ib[i]));
}
u->position += nframes;
return nframes;
}
template<typename T>
long data_cb_loop_input_only(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
{
struct user_state_loopback * u = (struct user_state_loopback *) user;
T * ib = (T *) inputbuffer;
if (outputbuffer != NULL) {
// Can't assert as it needs to return, so expect to fail instead
EXPECT_EQ(outputbuffer, (void *) NULL) << "outputbuffer should be null in input only callback";
return CUBEB_ERROR;
}
if (stream == NULL || inputbuffer == NULL) {
return CUBEB_ERROR;
}
std::lock_guard<std::mutex> lock(u->user_state_mutex);
for (int i = 0; i < nframes; i++) {
u->input_frames.push_back(ConvertSampleFromOutput(ib[i]));
}
return nframes;
}
template<typename T>
long data_cb_playback(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
{
struct user_state_loopback * u = (struct user_state_loopback *) user;
T * ob = (T *) outputbuffer;
if (stream == NULL || outputbuffer == NULL) {
return CUBEB_ERROR;
}
std::lock_guard<std::mutex> lock(u->user_state_mutex);
/* generate our test tone on the fly */
for (int i = 0; i < nframes; i++) {
double tone = 0.0;
if (u->position + i < NUM_FRAMES_TO_OUTPUT) {
/* generate sine wave */
tone = sin(2 * M_PI*(i + u->position) * TONE_FREQUENCY / SAMPLE_FREQUENCY);
tone *= OUTPUT_AMPLITUDE;
}
ob[i] = ConvertSampleToOutput<T>(tone);
u->output_frames.push_back(tone);
}
u->position += nframes;
return nframes;
}
void state_cb_loop(cubeb_stream * stream, void * /*user*/, cubeb_state state)
{
if (stream == NULL)
return;
switch (state) {
case CUBEB_STATE_STARTED:
fprintf(stderr, "stream started\n"); break;
case CUBEB_STATE_STOPPED:
fprintf(stderr, "stream stopped\n"); break;
case CUBEB_STATE_DRAINED:
fprintf(stderr, "stream drained\n"); break;
default:
fprintf(stderr, "unknown stream state %d\n", state);
}
return;
}
void run_loopback_duplex_test(bool is_float)
{
cubeb * ctx;
cubeb_stream * stream;
cubeb_stream_params input_params;
cubeb_stream_params output_params;
int r;
uint32_t latency_frames = 0;
r = common_init(&ctx, "Cubeb loopback example: duplex stream");
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
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);
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);
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);
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);
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright © 2017 Mozilla Foundation
*
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
*/
#include "gtest/gtest.h"
#if !defined(_XOPEN_SOURCE)
#define _XOPEN_SOURCE 600
#endif
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <memory>
#include <atomic>
#include "cubeb/cubeb.h"
//#define ENABLE_NORMAL_LOG
//#define ENABLE_VERBOSE_LOG
#include "common.h"
#define SAMPLE_FREQUENCY 48000
#define STREAM_FORMAT CUBEB_SAMPLE_S16LE
std::atomic<bool> load_callback{ false };
long data_cb(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
{
if (load_callback) {
fprintf(stderr, "Sleeping...\n");
delay(100000);
fprintf(stderr, "Sleeping done\n");
}
return nframes;
}
void state_cb(cubeb_stream * stream, void * /*user*/, cubeb_state state)
{
ASSERT_TRUE(!!stream);
switch (state) {
case CUBEB_STATE_STARTED:
fprintf(stderr, "stream started\n"); break;
case CUBEB_STATE_STOPPED:
fprintf(stderr, "stream stopped\n"); break;
case CUBEB_STATE_DRAINED:
FAIL() << "this test is not supposed to drain"; break;
case CUBEB_STATE_ERROR:
fprintf(stderr, "stream error\n"); break;
default:
FAIL() << "this test is not supposed to have a weird state"; break;
}
}
TEST(cubeb, overload_callback)
{
cubeb * ctx;
cubeb_stream * stream;
cubeb_stream_params output_params;
int r;
uint32_t latency_frames = 0;
r = common_init(&ctx, "Cubeb callback overload");
ASSERT_EQ(r, CUBEB_OK);
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
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);
}

116
externals/cubeb/test/test_record.cpp vendored Executable file
View File

@@ -0,0 +1,116 @@
/*
* Copyright © 2016 Mozilla Foundation
*
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
*/
/* libcubeb api/function test. Record the mic and check there is sound. */
#include "gtest/gtest.h"
#if !defined(_XOPEN_SOURCE)
#define _XOPEN_SOURCE 600
#endif
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <memory>
#include "cubeb/cubeb.h"
#include <atomic>
//#define ENABLE_NORMAL_LOG
//#define ENABLE_VERBOSE_LOG
#include "common.h"
#define SAMPLE_FREQUENCY 48000
#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
struct user_state_record
{
std::atomic<int> invalid_audio_value{ 0 };
};
long data_cb_record(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
{
user_state_record * u = reinterpret_cast<user_state_record*>(user);
float *b = (float *)inputbuffer;
if (stream == NULL || inputbuffer == NULL || outputbuffer != NULL) {
return CUBEB_ERROR;
}
for (long i = 0; i < nframes; i++) {
if (b[i] <= -1.0 || b[i] >= 1.0) {
u->invalid_audio_value = 1;
break;
}
}
return nframes;
}
void state_cb_record(cubeb_stream * stream, void * /*user*/, cubeb_state state)
{
if (stream == NULL)
return;
switch (state) {
case CUBEB_STATE_STARTED:
fprintf(stderr, "stream started\n"); break;
case CUBEB_STATE_STOPPED:
fprintf(stderr, "stream stopped\n"); break;
case CUBEB_STATE_DRAINED:
fprintf(stderr, "stream drained\n"); break;
default:
fprintf(stderr, "unknown stream state %d\n", state);
}
return;
}
TEST(cubeb, record)
{
if (cubeb_set_log_callback(CUBEB_LOG_DISABLED, nullptr /*print_log*/) != CUBEB_OK) {
fprintf(stderr, "Set log callback failed\n");
}
cubeb *ctx;
cubeb_stream *stream;
cubeb_stream_params params;
int r;
user_state_record stream_state;
r = common_init(&ctx, "Cubeb record example");
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
/* This test needs an available input device, skip it if this host does not
* have one. */
if (!has_available_input_device(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
}

1081
externals/cubeb/test/test_resampler.cpp vendored Executable file

File diff suppressed because it is too large Load Diff

73
externals/cubeb/test/test_ring_array.cpp vendored Executable file
View File

@@ -0,0 +1,73 @@
#include "gtest/gtest.h"
#ifdef __APPLE__
#include <string.h>
#include <iostream>
#include <CoreAudio/CoreAudioTypes.h>
#include "cubeb/cubeb.h"
#include "cubeb_ring_array.h"
TEST(cubeb, ring_array)
{
ring_array ra;
ASSERT_EQ(ring_array_init(&ra, 0, 0, 1, 1), CUBEB_ERROR_INVALID_PARAMETER);
ASSERT_EQ(ring_array_init(&ra, 1, 0, 0, 1), CUBEB_ERROR_INVALID_PARAMETER);
unsigned int capacity = 8;
ring_array_init(&ra, capacity, sizeof(int), 1, 1);
int verify_data[capacity] ;// {1,2,3,4,5,6,7,8};
AudioBuffer * p_data = NULL;
for (unsigned int i = 0; i < capacity; ++i) {
verify_data[i] = i; // in case capacity change value
*(int*)ra.buffer_array[i].mData = i;
ASSERT_EQ(ra.buffer_array[i].mDataByteSize, sizeof(int));
ASSERT_EQ(ra.buffer_array[i].mNumberChannels, 1u);
}
/* Get store buffers*/
for (unsigned int i = 0; i < capacity; ++i) {
p_data = ring_array_get_free_buffer(&ra);
ASSERT_NE(p_data, nullptr);
ASSERT_EQ(*(int*)p_data->mData, verify_data[i]);
}
/*Now array is full extra store should give NULL*/
ASSERT_EQ(ring_array_get_free_buffer(&ra), nullptr);
/* Get fetch buffers*/
for (unsigned int i = 0; i < capacity; ++i) {
p_data = ring_array_get_data_buffer(&ra);
ASSERT_NE(p_data, nullptr);
ASSERT_EQ(*(int*)p_data->mData, verify_data[i]);
}
/*Now array is empty extra fetch should give NULL*/
ASSERT_EQ(ring_array_get_data_buffer(&ra), nullptr);
p_data = NULL;
/* Repeated store fetch should can go for ever*/
for (unsigned int i = 0; i < 2*capacity; ++i) {
p_data = ring_array_get_free_buffer(&ra);
ASSERT_NE(p_data, nullptr);
ASSERT_EQ(ring_array_get_data_buffer(&ra), p_data);
}
p_data = NULL;
/* Verify/modify buffer data*/
for (unsigned int i = 0; i < capacity; ++i) {
p_data = ring_array_get_free_buffer(&ra);
ASSERT_NE(p_data, nullptr);
ASSERT_EQ(*((int*)p_data->mData), verify_data[i]);
(*((int*)p_data->mData))++; // Modify data
}
for (unsigned int i = 0; i < capacity; ++i) {
p_data = ring_array_get_data_buffer(&ra);
ASSERT_NE(p_data, nullptr);
ASSERT_EQ(*((int*)p_data->mData), verify_data[i]+1); // Verify modified data
}
ring_array_destroy(&ra);
}
#else
TEST(cubeb, DISABLED_ring_array)
{
}
#endif

227
externals/cubeb/test/test_ring_buffer.cpp vendored Executable file
View File

@@ -0,0 +1,227 @@
/*
* Copyright © 2016 Mozilla Foundation
*
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
*/
#define NOMINMAX
#include "gtest/gtest.h"
#include "cubeb_ringbuffer.h"
#include <iostream>
#include <thread>
#include <chrono>
/* Generate a monotonically increasing sequence of numbers. */
template<typename T>
class sequence_generator
{
public:
sequence_generator(size_t channels)
: channels(channels)
{ }
void get(T * elements, size_t frames)
{
for (size_t i = 0; i < frames; i++) {
for (size_t c = 0; c < channels; c++) {
elements[i * channels + c] = static_cast<T>(index_);
}
index_++;
}
}
void rewind(size_t frames)
{
index_ -= frames;
}
private:
size_t index_ = 0;
size_t channels = 0;
};
/* Checks that a sequence is monotonically increasing. */
template<typename T>
class sequence_verifier
{
public:
sequence_verifier(size_t channels)
: channels(channels)
{ }
void check(T * elements, size_t frames)
{
for (size_t i = 0; i < frames; i++) {
for (size_t c = 0; c < channels; c++) {
if (elements[i * channels + c] != static_cast<T>(index_)) {
std::cerr << "Element " << i << " is different. Expected "
<< static_cast<T>(index_) << ", got " << elements[i]
<< ". (channel count: " << channels << ")." << std::endl;
ASSERT_TRUE(false);
}
}
index_++;
}
}
private:
size_t index_ = 0;
size_t channels = 0;
};
template<typename T>
void test_ring(lock_free_audio_ring_buffer<T>& buf, int channels, int capacity_frames)
{
std::unique_ptr<T[]> seq(new T[capacity_frames * channels]);
sequence_generator<T> gen(channels);
sequence_verifier<T> checker(channels);
int iterations = 1002;
const int block_size = 128;
while(iterations--) {
gen.get(seq.get(), block_size);
int rv = buf.enqueue(seq.get(), block_size);
ASSERT_EQ(rv, block_size);
PodZero(seq.get(), block_size);
rv = buf.dequeue(seq.get(), block_size);
ASSERT_EQ(rv, block_size);
checker.check(seq.get(), block_size);
}
}
template<typename T>
void test_ring_multi(lock_free_audio_ring_buffer<T>& buf, int channels, int capacity_frames)
{
sequence_verifier<T> checker(channels);
std::unique_ptr<T[]> out_buffer(new T[capacity_frames * channels]);
const int block_size = 128;
std::thread t([=, &buf] {
int iterations = 1002;
std::unique_ptr<T[]> in_buffer(new T[capacity_frames * channels]);
sequence_generator<T> gen(channels);
while(iterations--) {
std::this_thread::yield();
gen.get(in_buffer.get(), block_size);
int rv = buf.enqueue(in_buffer.get(), block_size);
ASSERT_TRUE(rv <= block_size);
if (rv != block_size) {
gen.rewind(block_size - rv);
}
}
});
int remaining = 1002;
while(remaining--) {
std::this_thread::yield();
int rv = buf.dequeue(out_buffer.get(), block_size);
ASSERT_TRUE(rv <= block_size);
checker.check(out_buffer.get(), rv);
}
t.join();
}
template<typename T>
void basic_api_test(T& ring)
{
ASSERT_EQ(ring.capacity(), 128);
ASSERT_EQ(ring.available_read(), 0);
ASSERT_EQ(ring.available_write(), 128);
int rv = ring.enqueue_default(63);
ASSERT_TRUE(rv == 63);
ASSERT_EQ(ring.available_read(), 63);
ASSERT_EQ(ring.available_write(), 65);
rv = ring.enqueue_default(65);
ASSERT_EQ(rv, 65);
ASSERT_EQ(ring.available_read(), 128);
ASSERT_EQ(ring.available_write(), 0);
rv = ring.dequeue(nullptr, 63);
ASSERT_EQ(ring.available_read(), 65);
ASSERT_EQ(ring.available_write(), 63);
rv = ring.dequeue(nullptr, 65);
ASSERT_EQ(ring.available_read(), 0);
ASSERT_EQ(ring.available_write(), 128);
}
void test_reset_api() {
const size_t ring_buffer_size = 128;
const size_t enqueue_size = ring_buffer_size / 2;
lock_free_queue<float> ring(ring_buffer_size);
std::thread t([=, &ring] {
std::unique_ptr<float[]> in_buffer(new float[enqueue_size]);
ring.enqueue(in_buffer.get(), enqueue_size);
});
t.join();
ring.reset_thread_ids();
// Enqueue with a different thread. We have reset the thread ID
// in the ring buffer, this should work.
std::thread t2([=, &ring] {
std::unique_ptr<float[]> in_buffer(new float[enqueue_size]);
ring.enqueue(in_buffer.get(), enqueue_size);
});
t2.join();
ASSERT_TRUE(true);
}
TEST(cubeb, ring_buffer)
{
/* Basic API test. */
const int min_channels = 1;
const int max_channels = 10;
const int min_capacity = 199;
const int max_capacity = 1277;
const int capacity_increment = 27;
lock_free_queue<float> q1(128);
basic_api_test(q1);
lock_free_queue<short> q2(128);
basic_api_test(q2);
for (size_t channels = min_channels; channels < max_channels; channels++) {
lock_free_audio_ring_buffer<float> q3(channels, 128);
basic_api_test(q3);
lock_free_audio_ring_buffer<short> q4(channels, 128);
basic_api_test(q4);
}
/* Single thread testing. */
/* Test mono to 9.1 */
for (size_t channels = min_channels; channels < max_channels; channels++) {
/* Use non power-of-two numbers to catch edge-cases. */
for (size_t capacity_frames = min_capacity;
capacity_frames < max_capacity; capacity_frames+=capacity_increment) {
lock_free_audio_ring_buffer<float> ring(channels, capacity_frames);
test_ring(ring, channels, capacity_frames);
}
}
/* Multi thread testing */
for (size_t channels = min_channels; channels < max_channels; channels++) {
/* Use non power-of-two numbers to catch edge-cases. */
for (size_t capacity_frames = min_capacity;
capacity_frames < max_capacity; capacity_frames+=capacity_increment) {
lock_free_audio_ring_buffer<short> ring(channels, capacity_frames);
test_ring_multi(ring, channels, capacity_frames);
}
}
test_reset_api();
}

751
externals/cubeb/test/test_sanity.cpp vendored Executable file
View File

@@ -0,0 +1,751 @@
/*
* Copyright © 2011 Mozilla Foundation
*
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
*/
#include "gtest/gtest.h"
#if !defined(_XOPEN_SOURCE)
#define _XOPEN_SOURCE 600
#endif
#include "cubeb/cubeb.h"
#include <atomic>
#include <stdio.h>
#include <string.h>
#include <math.h>
//#define ENABLE_NORMAL_LOG
//#define ENABLE_VERBOSE_LOG
#include "common.h"
#define STREAM_RATE 44100
#define STREAM_LATENCY 100 * STREAM_RATE / 1000
#define STREAM_CHANNELS 1
#define STREAM_LAYOUT CUBEB_LAYOUT_MONO
#define STREAM_FORMAT CUBEB_SAMPLE_S16LE
int is_windows_7()
{
#ifdef __MINGW32__
fprintf(stderr, "Warning: this test was built with MinGW.\n"
"MinGW does not contain necessary version checking infrastructure. Claiming to be Windows 7, even if we're not.\n");
return 1;
#endif
#if (defined(_WIN32) || defined(__WIN32__)) && ( !defined(__MINGW32__))
OSVERSIONINFOEX osvi;
DWORDLONG condition_mask = 0;
ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
// NT 6.1 is Windows 7
osvi.dwMajorVersion = 6;
osvi.dwMinorVersion = 1;
VER_SET_CONDITION(condition_mask, VER_MAJORVERSION, VER_EQUAL);
VER_SET_CONDITION(condition_mask, VER_MINORVERSION, VER_GREATER_EQUAL);
return VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION, condition_mask);
#else
return 0;
#endif
}
static int dummy;
static std::atomic<uint64_t> total_frames_written;
static int delay_callback;
static long
test_data_callback(cubeb_stream * stm, void * user_ptr, const void * /*inputbuffer*/, void * outputbuffer, long nframes)
{
EXPECT_TRUE(stm && user_ptr == &dummy && outputbuffer && nframes > 0);
assert(outputbuffer);
memset(outputbuffer, 0, nframes * sizeof(short));
total_frames_written += nframes;
if (delay_callback) {
delay(10);
}
return nframes;
}
void
test_state_callback(cubeb_stream * /*stm*/, void * /*user_ptr*/, cubeb_state /*state*/)
{
}
TEST(cubeb, init_destroy_context)
{
int r;
cubeb * ctx;
char const* backend_id;
r = common_init(&ctx, "test_sanity");
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(ctx, nullptr);
backend_id = cubeb_get_backend_id(ctx);
ASSERT_TRUE(backend_id);
fprintf(stderr, "Backend: %s\n", backend_id);
cubeb_destroy(ctx);
}
TEST(cubeb, init_destroy_multiple_contexts)
{
size_t i;
int r;
cubeb * ctx[4];
int order[4] = {2, 0, 3, 1};
ASSERT_EQ(ARRAY_LENGTH(ctx), ARRAY_LENGTH(order));
for (i = 0; i < ARRAY_LENGTH(ctx); ++i) {
r = common_init(&ctx[i], NULL);
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(ctx[i], nullptr);
}
/* destroy in a different order */
for (i = 0; i < ARRAY_LENGTH(ctx); ++i) {
cubeb_destroy(ctx[order[i]]);
}
}
TEST(cubeb, context_variables)
{
int r;
cubeb * ctx;
uint32_t value;
cubeb_stream_params params;
r = common_init(&ctx, "test_context_variables");
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(ctx, nullptr);
params.channels = STREAM_CHANNELS;
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.layout = STREAM_LAYOUT;
params.prefs = CUBEB_STREAM_PREF_NONE;
r = cubeb_get_min_latency(ctx, &params, &value);
ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
if (r == CUBEB_OK) {
ASSERT_TRUE(value > 0);
}
r = cubeb_get_preferred_sample_rate(ctx, &value);
ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
if (r == CUBEB_OK) {
ASSERT_TRUE(value > 0);
}
cubeb_destroy(ctx);
}
TEST(cubeb, init_destroy_stream)
{
int r;
cubeb * ctx;
cubeb_stream * stream;
cubeb_stream_params params;
r = common_init(&ctx, "test_sanity");
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(ctx, nullptr);
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
params.layout = STREAM_LAYOUT;
params.prefs = CUBEB_STREAM_PREF_NONE;
r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(stream, nullptr);
cubeb_stream_destroy(stream);
cubeb_destroy(ctx);
}
TEST(cubeb, init_destroy_multiple_streams)
{
size_t i;
int r;
cubeb * ctx;
cubeb_stream * stream[8];
cubeb_stream_params params;
r = common_init(&ctx, "test_sanity");
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(ctx, nullptr);
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
params.layout = STREAM_LAYOUT;
params.prefs = CUBEB_STREAM_PREF_NONE;
for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
r = cubeb_stream_init(ctx, &stream[i], "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(stream[i], nullptr);
}
for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
cubeb_stream_destroy(stream[i]);
}
cubeb_destroy(ctx);
}
TEST(cubeb, configure_stream)
{
int r;
cubeb * ctx;
cubeb_stream * stream;
cubeb_stream_params params;
r = common_init(&ctx, "test_sanity");
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(ctx, nullptr);
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = 2;
params.layout = CUBEB_LAYOUT_STEREO;
params.prefs = CUBEB_STREAM_PREF_NONE;
r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(stream, nullptr);
r = cubeb_stream_set_volume(stream, 1.0f);
ASSERT_TRUE(r == 0 || r == CUBEB_ERROR_NOT_SUPPORTED);
r = cubeb_stream_set_name(stream, "test 2");
ASSERT_TRUE(r == 0 || r == CUBEB_ERROR_NOT_SUPPORTED);
cubeb_stream_destroy(stream);
cubeb_destroy(ctx);
}
TEST(cubeb, configure_stream_undefined_layout)
{
int r;
cubeb * ctx;
cubeb_stream * stream;
cubeb_stream_params params;
r = common_init(&ctx, "test_sanity");
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(ctx, nullptr);
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = 2;
params.layout = CUBEB_LAYOUT_UNDEFINED;
params.prefs = CUBEB_STREAM_PREF_NONE;
r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(stream, nullptr);
r = cubeb_stream_start(stream);
ASSERT_EQ(r, CUBEB_OK);
delay(100);
r = cubeb_stream_stop(stream);
ASSERT_EQ(r, CUBEB_OK);
cubeb_stream_destroy(stream);
cubeb_destroy(ctx);
}
static void
test_init_start_stop_destroy_multiple_streams(int early, int delay_ms)
{
size_t i;
int r;
cubeb * ctx;
cubeb_stream * stream[8];
cubeb_stream_params params;
r = common_init(&ctx, "test_sanity");
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(ctx, nullptr);
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
params.layout = STREAM_LAYOUT;
params.prefs = CUBEB_STREAM_PREF_NONE;
for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
r = cubeb_stream_init(ctx, &stream[i], "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(stream[i], nullptr);
if (early) {
r = cubeb_stream_start(stream[i]);
ASSERT_EQ(r, CUBEB_OK);
}
}
if (!early) {
for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
r = cubeb_stream_start(stream[i]);
ASSERT_EQ(r, CUBEB_OK);
}
}
if (delay_ms) {
delay(delay_ms);
}
if (!early) {
for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
r = cubeb_stream_stop(stream[i]);
ASSERT_EQ(r, CUBEB_OK);
}
}
for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
if (early) {
r = cubeb_stream_stop(stream[i]);
ASSERT_EQ(r, CUBEB_OK);
}
cubeb_stream_destroy(stream[i]);
}
cubeb_destroy(ctx);
}
TEST(cubeb, init_start_stop_destroy_multiple_streams)
{
/* Sometimes, when using WASAPI on windows 7 (vista and 8 are okay), and
* calling Activate a lot on an AudioClient, 0x800700b7 is returned. This is
* the HRESULT value for "Cannot create a file when that file already exists",
* and is not documented as a possible return value for this call. Hence, we
* try to limit the number of streams we create in this test. */
if (!is_windows_7()) {
delay_callback = 0;
test_init_start_stop_destroy_multiple_streams(0, 0);
test_init_start_stop_destroy_multiple_streams(1, 0);
test_init_start_stop_destroy_multiple_streams(0, 150);
test_init_start_stop_destroy_multiple_streams(1, 150);
delay_callback = 1;
test_init_start_stop_destroy_multiple_streams(0, 0);
test_init_start_stop_destroy_multiple_streams(1, 0);
test_init_start_stop_destroy_multiple_streams(0, 150);
test_init_start_stop_destroy_multiple_streams(1, 150);
}
}
TEST(cubeb, init_destroy_multiple_contexts_and_streams)
{
size_t i, j;
int r;
cubeb * ctx[2];
cubeb_stream * stream[8];
cubeb_stream_params params;
size_t streams_per_ctx = ARRAY_LENGTH(stream) / ARRAY_LENGTH(ctx);
ASSERT_EQ(ARRAY_LENGTH(ctx) * streams_per_ctx, ARRAY_LENGTH(stream));
/* Sometimes, when using WASAPI on windows 7 (vista and 8 are okay), and
* calling Activate a lot on an AudioClient, 0x800700b7 is returned. This is
* the HRESULT value for "Cannot create a file when that file already exists",
* and is not documented as a possible return value for this call. Hence, we
* try to limit the number of streams we create in this test. */
if (is_windows_7())
return;
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
params.layout = STREAM_LAYOUT;
params.prefs = CUBEB_STREAM_PREF_NONE;
for (i = 0; i < ARRAY_LENGTH(ctx); ++i) {
r = common_init(&ctx[i], "test_sanity");
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(ctx[i], nullptr);
for (j = 0; j < streams_per_ctx; ++j) {
r = cubeb_stream_init(ctx[i], &stream[i * streams_per_ctx + j], "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(stream[i * streams_per_ctx + j], nullptr);
}
}
for (i = 0; i < ARRAY_LENGTH(ctx); ++i) {
for (j = 0; j < streams_per_ctx; ++j) {
cubeb_stream_destroy(stream[i * streams_per_ctx + j]);
}
cubeb_destroy(ctx[i]);
}
}
TEST(cubeb, basic_stream_operations)
{
int r;
cubeb * ctx;
cubeb_stream * stream;
cubeb_stream_params params;
uint64_t position;
uint32_t latency;
r = common_init(&ctx, "test_sanity");
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(ctx, nullptr);
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
params.layout = STREAM_LAYOUT;
params.prefs = CUBEB_STREAM_PREF_NONE;
r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(stream, nullptr);
/* position and latency before stream has started */
r = cubeb_stream_get_position(stream, &position);
ASSERT_EQ(r, CUBEB_OK);
ASSERT_EQ(position, 0u);
r = cubeb_stream_get_latency(stream, &latency);
ASSERT_EQ(r, CUBEB_OK);
r = cubeb_stream_start(stream);
ASSERT_EQ(r, CUBEB_OK);
/* position and latency after while stream running */
r = cubeb_stream_get_position(stream, &position);
ASSERT_EQ(r, CUBEB_OK);
r = cubeb_stream_get_latency(stream, &latency);
ASSERT_EQ(r, CUBEB_OK);
r = cubeb_stream_stop(stream);
ASSERT_EQ(r, CUBEB_OK);
/* position and latency after stream has stopped */
r = cubeb_stream_get_position(stream, &position);
ASSERT_EQ(r, CUBEB_OK);
r = cubeb_stream_get_latency(stream, &latency);
ASSERT_EQ(r, CUBEB_OK);
cubeb_stream_destroy(stream);
cubeb_destroy(ctx);
}
TEST(cubeb, stream_position)
{
size_t i;
int r;
cubeb * ctx;
cubeb_stream * stream;
cubeb_stream_params params;
uint64_t position, last_position;
total_frames_written = 0;
r = common_init(&ctx, "test_sanity");
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(ctx, nullptr);
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
params.layout = STREAM_LAYOUT;
params.prefs = CUBEB_STREAM_PREF_NONE;
r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(stream, nullptr);
/* stream position should not advance before starting playback */
r = cubeb_stream_get_position(stream, &position);
ASSERT_EQ(r, CUBEB_OK);
ASSERT_EQ(position, 0u);
delay(500);
r = cubeb_stream_get_position(stream, &position);
ASSERT_EQ(r, CUBEB_OK);
ASSERT_EQ(position, 0u);
/* stream position should advance during playback */
r = cubeb_stream_start(stream);
ASSERT_EQ(r, CUBEB_OK);
/* XXX let start happen */
delay(500);
/* stream should have prefilled */
ASSERT_TRUE(total_frames_written.load() > 0);
r = cubeb_stream_get_position(stream, &position);
ASSERT_EQ(r, CUBEB_OK);
last_position = position;
delay(500);
r = cubeb_stream_get_position(stream, &position);
ASSERT_EQ(r, CUBEB_OK);
ASSERT_GE(position, last_position);
last_position = position;
/* stream position should not exceed total frames written */
for (i = 0; i < 5; ++i) {
r = cubeb_stream_get_position(stream, &position);
ASSERT_EQ(r, CUBEB_OK);
ASSERT_GE(position, last_position);
ASSERT_LE(position, total_frames_written.load());
last_position = position;
delay(500);
}
/* test that the position is valid even when starting and
* stopping the stream. */
for (i = 0; i < 5; ++i) {
r = cubeb_stream_stop(stream);
ASSERT_EQ(r, CUBEB_OK);
r = cubeb_stream_get_position(stream, &position);
ASSERT_EQ(r, CUBEB_OK);
ASSERT_TRUE(last_position < position);
last_position = position;
delay(500);
r = cubeb_stream_start(stream);
ASSERT_EQ(r, CUBEB_OK);
delay(500);
}
ASSERT_NE(last_position, 0u);
/* stream position should not advance after stopping playback */
r = cubeb_stream_stop(stream);
ASSERT_EQ(r, CUBEB_OK);
/* XXX allow stream to settle */
delay(500);
r = cubeb_stream_get_position(stream, &position);
ASSERT_EQ(r, CUBEB_OK);
last_position = position;
delay(500);
r = cubeb_stream_get_position(stream, &position);
ASSERT_EQ(r, CUBEB_OK);
ASSERT_EQ(position, last_position);
cubeb_stream_destroy(stream);
cubeb_destroy(ctx);
}
static std::atomic<int> do_drain;
static std::atomic<int> got_drain;
static long
test_drain_data_callback(cubeb_stream * stm, void * user_ptr, const void * /*inputbuffer*/, void * outputbuffer, long nframes)
{
EXPECT_TRUE(stm && user_ptr == &dummy && outputbuffer && nframes > 0);
assert(outputbuffer);
if (do_drain == 1) {
do_drain = 2;
return 0;
}
/* once drain has started, callback must never be called again */
EXPECT_TRUE(do_drain != 2);
memset(outputbuffer, 0, nframes * sizeof(short));
total_frames_written += nframes;
return nframes;
}
void
test_drain_state_callback(cubeb_stream * /*stm*/, void * /*user_ptr*/, cubeb_state state)
{
if (state == CUBEB_STATE_DRAINED) {
ASSERT_TRUE(!got_drain);
got_drain = 1;
}
}
TEST(cubeb, drain)
{
int r;
cubeb * ctx;
cubeb_stream * stream;
cubeb_stream_params params;
uint64_t position;
delay_callback = 0;
total_frames_written = 0;
r = common_init(&ctx, "test_sanity");
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(ctx, nullptr);
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
params.layout = STREAM_LAYOUT;
params.prefs = CUBEB_STREAM_PREF_NONE;
r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
test_drain_data_callback, test_drain_state_callback, &dummy);
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(stream, nullptr);
r = cubeb_stream_start(stream);
ASSERT_EQ(r, CUBEB_OK);
delay(500);
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, device_reset)
{
int r;
cubeb * ctx;
cubeb_stream * stream;
cubeb_stream_params params;
uint64_t position;
r = common_init(&ctx, "test_sanity");
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(ctx, nullptr);
if (strcmp(cubeb_get_backend_id(ctx), "wasapi")) {
// cubeb_stream_reset_default_device is only useful and implemented in the
// WASAPI backend.
return;
}
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);
r = cubeb_stream_start(stream);
ASSERT_EQ(r, CUBEB_OK);
uint32_t iterations = 5;
uint64_t previous_position = 0;
while (iterations--) {
r = cubeb_stream_get_position(stream, &position);
ASSERT_EQ(r, CUBEB_OK);
ASSERT_GE(position, previous_position);
previous_position = position;
delay(100);
}
r = cubeb_stream_reset_default_device(stream);
ASSERT_EQ(r, CUBEB_OK);
iterations = 5;
while (iterations--) {
r = cubeb_stream_get_position(stream, &position);
ASSERT_EQ(r, CUBEB_OK);
ASSERT_GE(position, previous_position);
previous_position = position;
delay(100);
}
cubeb_stream_stop(stream);
cubeb_stream_destroy(stream);
cubeb_destroy(ctx);
}
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);
}

121
externals/cubeb/test/test_tone.cpp vendored Executable file
View File

@@ -0,0 +1,121 @@
/*
* Copyright © 2011 Mozilla Foundation
*
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
*/
/* libcubeb api/function test. Plays a simple tone. */
#include "gtest/gtest.h"
#if !defined(_XOPEN_SOURCE)
#define _XOPEN_SOURCE 600
#endif
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <memory>
#include <limits.h>
#include "cubeb/cubeb.h"
#include <atomic>
//#define ENABLE_NORMAL_LOG
//#define ENABLE_VERBOSE_LOG
#include "common.h"
#define SAMPLE_FREQUENCY 48000
#define STREAM_FORMAT CUBEB_SAMPLE_S16LE
/* store the phase of the generated waveform */
struct cb_user_data {
std::atomic<long> position;
};
long data_cb_tone(cubeb_stream *stream, void *user, const void* /*inputbuffer*/, void *outputbuffer, long nframes)
{
struct cb_user_data *u = (struct cb_user_data *)user;
short *b = (short *)outputbuffer;
float t1, t2;
int i;
if (stream == NULL || u == NULL)
return CUBEB_ERROR;
/* generate our test tone on the fly */
for (i = 0; i < nframes; i++) {
/* North American dial tone */
t1 = sin(2*M_PI*(i + u->position)*350/SAMPLE_FREQUENCY);
t2 = sin(2*M_PI*(i + u->position)*440/SAMPLE_FREQUENCY);
b[i] = (SHRT_MAX / 2) * t1;
b[i] += (SHRT_MAX / 2) * t2;
/* European dial tone */
/*
t1 = sin(2*M_PI*(i + u->position)*425/SAMPLE_FREQUENCY);
b[i] = SHRT_MAX * t1;
*/
}
/* remember our phase to avoid clicking on buffer transitions */
/* we'll still click if position overflows */
u->position += nframes;
return nframes;
}
void state_cb_tone(cubeb_stream *stream, void *user, cubeb_state state)
{
struct cb_user_data *u = (struct cb_user_data *)user;
if (stream == NULL || u == NULL)
return;
switch (state) {
case CUBEB_STATE_STARTED:
fprintf(stderr, "stream started\n"); break;
case CUBEB_STATE_STOPPED:
fprintf(stderr, "stream stopped\n"); break;
case CUBEB_STATE_DRAINED:
fprintf(stderr, "stream drained\n"); break;
default:
fprintf(stderr, "unknown stream state %d\n", state);
}
return;
}
TEST(cubeb, tone)
{
cubeb *ctx;
cubeb_stream *stream;
cubeb_stream_params params;
int r;
r = common_init(&ctx, "Cubeb tone example");
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
params.format = STREAM_FORMAT;
params.rate = SAMPLE_FREQUENCY;
params.channels = 1;
params.layout = CUBEB_LAYOUT_MONO;
params.prefs = CUBEB_STREAM_PREF_NONE;
std::unique_ptr<cb_user_data> user_data(new cb_user_data());
ASSERT_TRUE(!!user_data) << "Error allocating user data";
user_data->position = 0;
r = cubeb_stream_init(ctx, &stream, "Cubeb tone (mono)", NULL, NULL, NULL, &params,
4096, data_cb_tone, state_cb_tone, user_data.get());
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
cleanup_stream_at_exit(stream, cubeb_stream_destroy);
cubeb_stream_start(stream);
delay(500);
cubeb_stream_stop(stream);
ASSERT_TRUE(user_data->position.load());
}

72
externals/cubeb/test/test_utils.cpp vendored Executable file
View File

@@ -0,0 +1,72 @@
#include "gtest/gtest.h"
#include "cubeb_utils.h"
TEST(cubeb, auto_array)
{
auto_array<uint32_t> array;
auto_array<uint32_t> array2(10);
uint32_t a[10];
ASSERT_EQ(array2.length(), 0u);
ASSERT_EQ(array2.capacity(), 10u);
for (uint32_t i = 0; i < 10; i++) {
a[i] = i;
}
ASSERT_EQ(array.capacity(), 0u);
ASSERT_EQ(array.length(), 0u);
array.push(a, 10);
ASSERT_TRUE(!array.reserve(9));
for (uint32_t i = 0; i < 10; i++) {
ASSERT_EQ(array.data()[i], i);
}
ASSERT_EQ(array.capacity(), 10u);
ASSERT_EQ(array.length(), 10u);
uint32_t b[10];
array.pop(b, 5);
ASSERT_EQ(array.capacity(), 10u);
ASSERT_EQ(array.length(), 5u);
for (uint32_t i = 0; i < 5; i++) {
ASSERT_EQ(b[i], i);
ASSERT_EQ(array.data()[i], 5 + i);
}
uint32_t* bb = b + 5;
array.pop(bb, 5);
ASSERT_EQ(array.capacity(), 10u);
ASSERT_EQ(array.length(), 0u);
for (uint32_t i = 0; i < 5; i++) {
ASSERT_EQ(bb[i], 5 + i);
}
ASSERT_TRUE(!array.pop(nullptr, 1));
array.push(a, 10);
array.push(a, 10);
for (uint32_t j = 0; j < 2; j++) {
for (uint32_t i = 0; i < 10; i++) {
ASSERT_EQ(array.data()[10 * j + i], i);
}
}
ASSERT_EQ(array.length(), 20u);
ASSERT_EQ(array.capacity(), 20u);
array.pop(nullptr, 5);
for (uint32_t i = 0; i < 5; i++) {
ASSERT_EQ(array.data()[i], 5 + i);
}
ASSERT_EQ(array.length(), 15u);
ASSERT_EQ(array.capacity(), 20u);
}