early-access version 1255
This commit is contained in:
13
externals/cubeb/test/README.md
vendored
Executable file
13
externals/cubeb/test/README.md
vendored
Executable 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
145
externals/cubeb/test/common.h
vendored
Executable 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
244
externals/cubeb/test/test_audio.cpp
vendored
Executable 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, ¶ms,
|
||||
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, ¶ms,
|
||||
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
207
externals/cubeb/test/test_callback_ret.cpp
vendored
Executable 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
260
externals/cubeb/test/test_deadlock.cpp
vendored
Executable 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,
|
||||
¶ms, 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);
|
||||
}
|
109
externals/cubeb/test/test_device_changed_callback.cpp
vendored
Executable file
109
externals/cubeb/test/test_device_changed_callback.cpp
vendored
Executable 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
255
externals/cubeb/test/test_devices.cpp
vendored
Executable 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
181
externals/cubeb/test/test_duplex.cpp
vendored
Executable 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
47
externals/cubeb/test/test_latency.cpp
vendored
Executable 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, ¶ms, &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
578
externals/cubeb/test/test_loopback.cpp
vendored
Executable 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);
|
||||
}
|
92
externals/cubeb/test/test_overload_callback.cpp
vendored
Executable file
92
externals/cubeb/test/test_overload_callback.cpp
vendored
Executable 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
116
externals/cubeb/test/test_record.cpp
vendored
Executable 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, ¶ms, 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
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
73
externals/cubeb/test/test_ring_array.cpp
vendored
Executable 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
227
externals/cubeb/test/test_ring_buffer.cpp
vendored
Executable 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
751
externals/cubeb/test/test_sanity.cpp
vendored
Executable 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, ¶ms, &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, ¶ms, 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, ¶ms, 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, ¶ms, 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, ¶ms, 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, ¶ms, 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, ¶ms, 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, ¶ms, 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, ¶ms, 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, ¶ms, 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, ¶ms, 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
121
externals/cubeb/test/test_tone.cpp
vendored
Executable 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, ¶ms,
|
||||
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
72
externals/cubeb/test/test_utils.cpp
vendored
Executable 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);
|
||||
}
|
||||
|
Reference in New Issue
Block a user