1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
|
/*
* Copyright � 2017 Mozilla Foundation
*
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
*/
/* libcubeb api/function test. Test that different return values from user
specified callbacks are handled correctly. */
#include "gtest/gtest.h"
#if !defined(_XOPEN_SOURCE)
#define _XOPEN_SOURCE 600
#endif
#include "cubeb/cubeb.h"
#include <atomic>
#include <memory>
#include <string>
// #define ENABLE_NORMAL_LOG
// #define ENABLE_VERBOSE_LOG
#include "common.h"
const uint32_t SAMPLE_FREQUENCY = 48000;
const cubeb_sample_format SAMPLE_FORMAT = CUBEB_SAMPLE_S16NE;
enum test_direction { INPUT_ONLY, OUTPUT_ONLY, DUPLEX };
// Structure which is used by data callbacks to track the total callbacks
// executed vs the number of callbacks expected.
struct user_state_callback_ret {
std::atomic<int> cb_count{0};
std::atomic<int> expected_cb_count{0};
std::atomic<int> error_state{0};
};
// Data callback that always returns 0
long
data_cb_ret_zero(cubeb_stream * stream, void * user, const void * inputbuffer,
void * outputbuffer, long nframes)
{
user_state_callback_ret * u = (user_state_callback_ret *)user;
// If this is the first time the callback has been called set our expected
// callback count
if (u->cb_count == 0) {
u->expected_cb_count = 1;
}
u->cb_count++;
if (nframes < 1) {
// This shouldn't happen
EXPECT_TRUE(false) << "nframes should not be 0 in data callback!";
}
return 0;
}
// Data callback that always returns nframes - 1
long
data_cb_ret_nframes_minus_one(cubeb_stream * stream, void * user,
const void * inputbuffer, void * outputbuffer,
long nframes)
{
user_state_callback_ret * u = (user_state_callback_ret *)user;
// If this is the first time the callback has been called set our expected
// callback count
if (u->cb_count == 0) {
u->expected_cb_count = 1;
}
u->cb_count++;
if (nframes < 1) {
// This shouldn't happen
EXPECT_TRUE(false) << "nframes should not be 0 in data callback!";
}
if (outputbuffer != NULL) {
// If we have an output buffer insert silence
short * ob = (short *)outputbuffer;
for (long i = 0; i < nframes - 1; i++) {
ob[i] = 0;
}
}
return nframes - 1;
}
// Data callback that always returns nframes
long
data_cb_ret_nframes(cubeb_stream * stream, void * user,
const void * inputbuffer, void * outputbuffer, long nframes)
{
user_state_callback_ret * u = (user_state_callback_ret *)user;
u->cb_count++;
// Every callback returns nframes, so every callback is expected
u->expected_cb_count++;
if (nframes < 1) {
// This shouldn't happen
EXPECT_TRUE(false) << "nframes should not be 0 in data callback!";
}
if (outputbuffer != NULL) {
// If we have an output buffer insert silence
short * ob = (short *)outputbuffer;
for (long i = 0; i < nframes; i++) {
ob[i] = 0;
}
}
return nframes;
}
// Data callback that always returns CUBEB_ERROR
long
data_cb_ret_error(cubeb_stream * stream, void * user, const void * inputbuffer,
void * outputbuffer, long nframes)
{
user_state_callback_ret * u = (user_state_callback_ret *)user;
// If this is the first time the callback has been called set our expected
// callback count
if (u->cb_count == 0) {
u->expected_cb_count = 1;
}
u->cb_count++;
if (nframes < 1) {
// This shouldn't happen
EXPECT_TRUE(false) << "nframes should not be 0 in data callback!";
}
return CUBEB_ERROR;
}
void
state_cb_ret(cubeb_stream * stream, void * user, cubeb_state state)
{
if (stream == NULL)
return;
user_state_callback_ret * u = (user_state_callback_ret *)user;
switch (state) {
case CUBEB_STATE_STARTED:
fprintf(stderr, "stream started\n");
break;
case CUBEB_STATE_STOPPED:
fprintf(stderr, "stream stopped\n");
break;
case CUBEB_STATE_DRAINED:
fprintf(stderr, "stream drained\n");
break;
case CUBEB_STATE_ERROR:
fprintf(stderr, "stream error\n");
u->error_state.fetch_add(1);
break;
default:
fprintf(stderr, "unknown stream state %d\n", state);
}
}
void
run_test_callback(test_direction direction, cubeb_data_callback data_cb,
const std::string & test_desc)
{
cubeb * ctx;
cubeb_stream * stream;
cubeb_stream_params input_params;
cubeb_stream_params output_params;
int r;
user_state_callback_ret user_state;
uint32_t latency_frames = 0;
r = common_init(&ctx, "Cubeb callback return value example");
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit(
ctx, cubeb_destroy);
if ((direction == INPUT_ONLY || direction == DUPLEX) &&
!can_run_audio_input_test(ctx)) {
/* This test needs an available input device, skip it if this host does not
* have one or if the backend doesn't implement input. */
return;
}
// Setup all params, but only pass them later as required by direction
input_params.format = SAMPLE_FORMAT;
input_params.rate = SAMPLE_FREQUENCY;
input_params.channels = 1;
input_params.layout = CUBEB_LAYOUT_MONO;
input_params.prefs = CUBEB_STREAM_PREF_NONE;
output_params = input_params;
r = cubeb_get_min_latency(ctx, &input_params, &latency_frames);
if (r != CUBEB_OK) {
// not fatal
latency_frames = 1024;
}
switch (direction) {
case INPUT_ONLY:
r = cubeb_stream_init(ctx, &stream, "Cubeb callback ret input", NULL,
&input_params, NULL, NULL, latency_frames, data_cb,
state_cb_ret, &user_state);
break;
case OUTPUT_ONLY:
r = cubeb_stream_init(ctx, &stream, "Cubeb callback ret output", NULL, NULL,
NULL, &output_params, latency_frames, data_cb,
state_cb_ret, &user_state);
break;
case DUPLEX:
r = cubeb_stream_init(ctx, &stream, "Cubeb callback ret duplex", NULL,
&input_params, NULL, &output_params, latency_frames,
data_cb, state_cb_ret, &user_state);
break;
default:
ASSERT_TRUE(false) << "Unrecognized test direction!";
}
EXPECT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
cleanup_stream_at_exit(stream, cubeb_stream_destroy);
cubeb_stream_start(stream);
delay(100);
cubeb_stream_stop(stream);
ASSERT_EQ(user_state.expected_cb_count, user_state.cb_count)
<< "Callback called unexpected number of times for " << test_desc << "!";
// TODO: On some test configurations, the data_callback is never called.
if (data_cb == data_cb_ret_error && user_state.cb_count != 0) {
ASSERT_EQ(user_state.error_state, 1) << "Callback expected error state";
}
}
TEST(cubeb, test_input_callback)
{
run_test_callback(INPUT_ONLY, data_cb_ret_zero, "input only, return 0");
run_test_callback(INPUT_ONLY, data_cb_ret_nframes_minus_one,
"input only, return nframes - 1");
run_test_callback(INPUT_ONLY, data_cb_ret_nframes,
"input only, return nframes");
run_test_callback(INPUT_ONLY, data_cb_ret_error,
"input only, return CUBEB_ERROR");
}
TEST(cubeb, test_output_callback)
{
run_test_callback(OUTPUT_ONLY, data_cb_ret_zero, "output only, return 0");
run_test_callback(OUTPUT_ONLY, data_cb_ret_nframes_minus_one,
"output only, return nframes - 1");
run_test_callback(OUTPUT_ONLY, data_cb_ret_nframes,
"output only, return nframes");
run_test_callback(OUTPUT_ONLY, data_cb_ret_error,
"output only, return CUBEB_ERROR");
}
TEST(cubeb, test_duplex_callback)
{
run_test_callback(DUPLEX, data_cb_ret_zero, "duplex, return 0");
run_test_callback(DUPLEX, data_cb_ret_nframes_minus_one,
"duplex, return nframes - 1");
run_test_callback(DUPLEX, data_cb_ret_nframes, "duplex, return nframes");
run_test_callback(DUPLEX, data_cb_ret_error, "duplex, return CUBEB_ERROR");
}
|