test_duplex.cpp (12080B)
1 /* 2 * Copyright © 2016 Mozilla Foundation 3 * 4 * This program is made available under an ISC-style license. See the 5 * accompanying file LICENSE for details. 6 */ 7 8 /* libcubeb api/function test. Loops input back to output and check audio 9 * is flowing. */ 10 #include "gtest/gtest.h" 11 #if !defined(_XOPEN_SOURCE) 12 #define _XOPEN_SOURCE 600 13 #endif 14 #include "cubeb/cubeb.h" 15 #include <atomic> 16 #include <math.h> 17 #include <memory> 18 #include <stdio.h> 19 #include <stdlib.h> 20 #ifdef __APPLE__ 21 #include <sys/utsname.h> 22 #endif 23 24 #include "mozilla/gtest/MozHelpers.h" 25 26 // #define ENABLE_NORMAL_LOG 27 // #define ENABLE_VERBOSE_LOG 28 #include "common.h" 29 30 #define SAMPLE_FREQUENCY 48000 31 #define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE 32 #define INPUT_CHANNELS 1 33 #define INPUT_LAYOUT CUBEB_LAYOUT_MONO 34 #define OUTPUT_CHANNELS 2 35 #define OUTPUT_LAYOUT CUBEB_LAYOUT_STEREO 36 37 struct user_state_duplex { 38 std::atomic<int> invalid_audio_value{0}; 39 }; 40 41 long 42 data_cb_duplex(cubeb_stream * stream, void * user, const void * inputbuffer, 43 void * outputbuffer, long nframes) 44 { 45 user_state_duplex * u = reinterpret_cast<user_state_duplex *>(user); 46 float * ib = (float *)inputbuffer; 47 float * ob = (float *)outputbuffer; 48 49 if (stream == NULL || inputbuffer == NULL || outputbuffer == NULL) { 50 return CUBEB_ERROR; 51 } 52 53 // Loop back: upmix the single input channel to the two output channels, 54 // checking if there is noise in the process. 55 long output_index = 0; 56 for (long i = 0; i < nframes; i++) { 57 if (ib[i] <= -1.0 || ib[i] >= 1.0) { 58 u->invalid_audio_value = 1; 59 } 60 ob[output_index] = ob[output_index + 1] = ib[i]; 61 output_index += 2; 62 } 63 64 return nframes; 65 } 66 67 void 68 state_cb_duplex(cubeb_stream * stream, void * /*user*/, cubeb_state state) 69 { 70 if (stream == NULL) 71 return; 72 73 switch (state) { 74 case CUBEB_STATE_STARTED: 75 fprintf(stderr, "stream started\n"); 76 break; 77 case CUBEB_STATE_STOPPED: 78 fprintf(stderr, "stream stopped\n"); 79 break; 80 case CUBEB_STATE_DRAINED: 81 fprintf(stderr, "stream drained\n"); 82 break; 83 default: 84 fprintf(stderr, "unknown stream state %d\n", state); 85 } 86 87 return; 88 } 89 90 TEST(cubeb, duplex) 91 { 92 cubeb * ctx; 93 cubeb_stream * stream; 94 cubeb_stream_params input_params; 95 cubeb_stream_params output_params; 96 int r; 97 user_state_duplex stream_state; 98 uint32_t latency_frames = 0; 99 100 r = common_init(&ctx, "Cubeb duplex example"); 101 ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library"; 102 103 std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit( 104 ctx, cubeb_destroy); 105 106 /* This test needs an available input device, skip it if this host does not 107 * have one. */ 108 if (!can_run_audio_input_test(ctx)) { 109 return; 110 } 111 112 /* typical user-case: mono input, stereo output, low latency. */ 113 input_params.format = STREAM_FORMAT; 114 input_params.rate = SAMPLE_FREQUENCY; 115 input_params.channels = INPUT_CHANNELS; 116 input_params.layout = INPUT_LAYOUT; 117 input_params.prefs = CUBEB_STREAM_PREF_NONE; 118 output_params.format = STREAM_FORMAT; 119 output_params.rate = SAMPLE_FREQUENCY; 120 output_params.channels = OUTPUT_CHANNELS; 121 output_params.layout = OUTPUT_LAYOUT; 122 output_params.prefs = CUBEB_STREAM_PREF_NONE; 123 124 r = cubeb_get_min_latency(ctx, &output_params, &latency_frames); 125 ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency"; 126 127 r = cubeb_stream_init(ctx, &stream, "Cubeb duplex", NULL, &input_params, NULL, 128 &output_params, latency_frames, data_cb_duplex, 129 state_cb_duplex, &stream_state); 130 ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream"; 131 132 std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)> 133 cleanup_stream_at_exit(stream, cubeb_stream_destroy); 134 135 cubeb_stream_start(stream); 136 delay(500); 137 cubeb_stream_stop(stream); 138 139 ASSERT_FALSE(stream_state.invalid_audio_value.load()); 140 } 141 142 void 143 device_collection_changed_callback(cubeb * context, void * user) 144 { 145 fprintf(stderr, "collection changed callback\n"); 146 ASSERT_TRUE(false) << "Error: device collection changed callback" 147 " called when opening a stream"; 148 } 149 150 void 151 duplex_collection_change_impl(cubeb * ctx) 152 { 153 cubeb_stream * stream; 154 cubeb_stream_params input_params; 155 cubeb_stream_params output_params; 156 int r; 157 uint32_t latency_frames = 0; 158 159 r = cubeb_register_device_collection_changed( 160 ctx, static_cast<cubeb_device_type>(CUBEB_DEVICE_TYPE_INPUT), 161 device_collection_changed_callback, nullptr); 162 ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream"; 163 164 /* typical user-case: mono input, stereo output, low latency. */ 165 input_params.format = STREAM_FORMAT; 166 input_params.rate = SAMPLE_FREQUENCY; 167 input_params.channels = INPUT_CHANNELS; 168 input_params.layout = INPUT_LAYOUT; 169 input_params.prefs = CUBEB_STREAM_PREF_NONE; 170 output_params.format = STREAM_FORMAT; 171 output_params.rate = SAMPLE_FREQUENCY; 172 output_params.channels = OUTPUT_CHANNELS; 173 output_params.layout = OUTPUT_LAYOUT; 174 output_params.prefs = CUBEB_STREAM_PREF_NONE; 175 176 r = cubeb_get_min_latency(ctx, &output_params, &latency_frames); 177 ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency"; 178 179 r = cubeb_stream_init(ctx, &stream, "Cubeb duplex", NULL, &input_params, NULL, 180 &output_params, latency_frames, data_cb_duplex, 181 state_cb_duplex, nullptr); 182 ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream"; 183 cubeb_stream_destroy(stream); 184 } 185 186 TEST(cubeb, duplex_collection_change) 187 { 188 cubeb * ctx; 189 int r; 190 191 r = common_init(&ctx, "Cubeb duplex example with collection change"); 192 ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library"; 193 std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit( 194 ctx, cubeb_destroy); 195 196 /* This test needs an available input device, skip it if this host does not 197 * have one. */ 198 if (!can_run_audio_input_test(ctx)) { 199 return; 200 } 201 202 duplex_collection_change_impl(ctx); 203 r = cubeb_register_device_collection_changed( 204 ctx, static_cast<cubeb_device_type>(CUBEB_DEVICE_TYPE_INPUT), nullptr, 205 nullptr); 206 ASSERT_EQ(r, CUBEB_OK); 207 } 208 209 void CauseDeath(cubeb * p) { 210 mozilla::gtest::DisableCrashReporter(); 211 cubeb_destroy(p); 212 } 213 214 #ifdef GTEST_HAS_DEATH_TEST 215 TEST(cubeb, duplex_collection_change_no_unregister) 216 { 217 cubeb * ctx; 218 int r; 219 220 r = common_init(&ctx, "Cubeb duplex example with collection change"); 221 ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library"; 222 223 /* This test needs an available input device, skip it if this host does not 224 * have one. */ 225 if (!can_run_audio_input_test(ctx)) { 226 cubeb_destroy(ctx); 227 return; 228 } 229 230 std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit( 231 ctx, [](cubeb * p) noexcept { EXPECT_DEATH(CauseDeath(p), ""); }); 232 233 duplex_collection_change_impl(ctx); 234 235 # if defined(XP_MACOSX) && !defined(MOZ_DEBUG) 236 // For some reason this test hangs on macOS in non-debug builds when the child 237 // process (death test fork) crashes and the crash reporter is enabled in the 238 // parent process. There is not much left to do that can cause a crash in the 239 // parent process anyway, so disable the crash reporter where needed to pass. 240 mozilla::gtest::DisableCrashReporter(); 241 # endif 242 } 243 #endif 244 245 long 246 data_cb_input(cubeb_stream * stream, void * user, const void * inputbuffer, 247 void * outputbuffer, long nframes) 248 { 249 if (stream == NULL || inputbuffer == NULL || outputbuffer != NULL) { 250 return CUBEB_ERROR; 251 } 252 253 return nframes; 254 } 255 256 void 257 state_cb_input(cubeb_stream * stream, void * /*user*/, cubeb_state state) 258 { 259 if (stream == NULL) 260 return; 261 262 switch (state) { 263 case CUBEB_STATE_STARTED: 264 fprintf(stderr, "stream started\n"); 265 break; 266 case CUBEB_STATE_STOPPED: 267 fprintf(stderr, "stream stopped\n"); 268 break; 269 case CUBEB_STATE_DRAINED: 270 fprintf(stderr, "stream drained\n"); 271 break; 272 case CUBEB_STATE_ERROR: 273 fprintf(stderr, "stream runs into error state\n"); 274 break; 275 default: 276 fprintf(stderr, "unknown stream state %d\n", state); 277 } 278 279 return; 280 } 281 282 std::vector<cubeb_devid> 283 get_devices(cubeb * ctx, cubeb_device_type type) 284 { 285 std::vector<cubeb_devid> devices; 286 287 cubeb_device_collection collection; 288 int r = cubeb_enumerate_devices(ctx, type, &collection); 289 290 if (r != CUBEB_OK) { 291 fprintf(stderr, "Failed to enumerate devices\n"); 292 return devices; 293 } 294 295 for (uint32_t i = 0; i < collection.count; i++) { 296 if (collection.device[i].state == CUBEB_DEVICE_STATE_ENABLED) { 297 devices.emplace_back(collection.device[i].devid); 298 } 299 } 300 301 cubeb_device_collection_destroy(ctx, &collection); 302 303 return devices; 304 } 305 306 TEST(cubeb, one_duplex_one_input) 307 { 308 cubeb * ctx; 309 cubeb_stream * duplex_stream; 310 cubeb_stream_params input_params; 311 cubeb_stream_params output_params; 312 int r; 313 user_state_duplex duplex_stream_state; 314 uint32_t latency_frames = 0; 315 316 // Disabled on 10.15, see bug 1867183 317 #ifdef __APPLE__ 318 struct utsname uts; 319 uname(&uts); 320 // 10.15 correspond to Darwin 19 321 if (strncmp(uts.release, "19", 2) == 0) { 322 printf("Test disabled on macOS 10.15, exiting.\n"); 323 return; 324 } 325 #endif 326 327 r = common_init(&ctx, "Cubeb duplex example"); 328 ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library"; 329 330 std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit( 331 ctx, cubeb_destroy); 332 333 /* This test needs at least two available input devices. */ 334 std::vector<cubeb_devid> input_devices = 335 get_devices(ctx, CUBEB_DEVICE_TYPE_INPUT); 336 if (input_devices.size() < 2) { 337 return; 338 } 339 340 /* This test needs at least one available output device. */ 341 std::vector<cubeb_devid> output_devices = 342 get_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT); 343 if (output_devices.size() < 1) { 344 return; 345 } 346 347 cubeb_devid duplex_input = input_devices.front(); 348 cubeb_devid duplex_output = nullptr; // default device 349 cubeb_devid input_only = input_devices.back(); 350 351 /* typical use-case: mono voice input, stereo output, low latency. */ 352 input_params.format = STREAM_FORMAT; 353 input_params.rate = SAMPLE_FREQUENCY; 354 input_params.channels = INPUT_CHANNELS; 355 input_params.layout = CUBEB_LAYOUT_UNDEFINED; 356 input_params.prefs = CUBEB_STREAM_PREF_VOICE; 357 358 output_params.format = STREAM_FORMAT; 359 output_params.rate = SAMPLE_FREQUENCY; 360 output_params.channels = OUTPUT_CHANNELS; 361 output_params.layout = OUTPUT_LAYOUT; 362 output_params.prefs = CUBEB_STREAM_PREF_NONE; 363 364 r = cubeb_get_min_latency(ctx, &output_params, &latency_frames); 365 ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency"; 366 367 r = cubeb_stream_init(ctx, &duplex_stream, "Cubeb duplex", duplex_input, 368 &input_params, duplex_output, &output_params, 369 latency_frames, data_cb_duplex, state_cb_duplex, 370 &duplex_stream_state); 371 ASSERT_EQ(r, CUBEB_OK) << "Error initializing duplex cubeb stream"; 372 373 std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)> 374 cleanup_stream_at_exit(duplex_stream, cubeb_stream_destroy); 375 376 r = cubeb_stream_start(duplex_stream); 377 ASSERT_EQ(r, CUBEB_OK) << "Could not start duplex stream"; 378 delay(500); 379 380 cubeb_stream * input_stream; 381 r = cubeb_stream_init(ctx, &input_stream, "Cubeb input", input_only, 382 &input_params, NULL, NULL, latency_frames, 383 data_cb_input, state_cb_input, nullptr); 384 ASSERT_EQ(r, CUBEB_OK) << "Error initializing input-only cubeb stream"; 385 386 std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)> 387 cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy); 388 389 r = cubeb_stream_start(input_stream); 390 ASSERT_EQ(r, CUBEB_OK) << "Could not start input stream"; 391 delay(500); 392 393 r = cubeb_stream_stop(duplex_stream); 394 ASSERT_EQ(r, CUBEB_OK) << "Could not stop duplex stream"; 395 396 r = cubeb_stream_stop(input_stream); 397 ASSERT_EQ(r, CUBEB_OK) << "Could not stop input stream"; 398 399 ASSERT_FALSE(duplex_stream_state.invalid_audio_value.load()); 400 } 401 402 #undef SAMPLE_FREQUENCY 403 #undef STREAM_FORMAT 404 #undef INPUT_CHANNELS 405 #undef INPUT_LAYOUT 406 #undef OUTPUT_CHANNELS 407 #undef OUTPUT_LAYOUT