test_loopback.cpp (23779B)
1 /* 2 * Copyright © 2017 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. Requests a loopback device and checks that 9 output is being looped back to input. NOTE: Usage of output devices while 10 performing this test will cause flakey results! */ 11 #include "gtest/gtest.h" 12 #if !defined(_XOPEN_SOURCE) 13 #define _XOPEN_SOURCE 600 14 #endif 15 #include "cubeb/cubeb.h" 16 #include <algorithm> 17 #include <math.h> 18 #include <memory> 19 #include <mutex> 20 #include <stdio.h> 21 #include <stdlib.h> 22 #include <string> 23 // #define ENABLE_NORMAL_LOG 24 // #define ENABLE_VERBOSE_LOG 25 #include "common.h" 26 const uint32_t SAMPLE_FREQUENCY = 48000; 27 const uint32_t TONE_FREQUENCY = 440; 28 const double OUTPUT_AMPLITUDE = 0.25; 29 const int32_t NUM_FRAMES_TO_OUTPUT = 30 SAMPLE_FREQUENCY / 20; /* play ~50ms of samples */ 31 32 template <typename T> 33 T 34 ConvertSampleToOutput(double input); 35 template <> 36 float 37 ConvertSampleToOutput(double input) 38 { 39 return float(input); 40 } 41 template <> 42 short 43 ConvertSampleToOutput(double input) 44 { 45 return short(input * 32767.0f); 46 } 47 48 template <typename T> 49 double 50 ConvertSampleFromOutput(T sample); 51 template <> 52 double 53 ConvertSampleFromOutput(float sample) 54 { 55 return double(sample); 56 } 57 template <> 58 double 59 ConvertSampleFromOutput(short sample) 60 { 61 return double(sample / 32767.0); 62 } 63 64 /* Simple cross correlation to help find phase shift. Not a performant impl */ 65 std::vector<double> 66 cross_correlate(std::vector<double> & f, std::vector<double> & g, 67 size_t signal_length) 68 { 69 /* the length we sweep our window through to find the cross correlation */ 70 size_t sweep_length = f.size() - signal_length + 1; 71 std::vector<double> correlation; 72 correlation.reserve(sweep_length); 73 for (size_t i = 0; i < sweep_length; i++) { 74 double accumulator = 0.0; 75 for (size_t j = 0; j < signal_length; j++) { 76 accumulator += f.at(j) * g.at(i + j); 77 } 78 correlation.push_back(accumulator); 79 } 80 return correlation; 81 } 82 83 /* best effort discovery of phase shift between output and (looped) input*/ 84 size_t 85 find_phase(std::vector<double> & output_frames, 86 std::vector<double> & input_frames, size_t signal_length) 87 { 88 std::vector<double> correlation = 89 cross_correlate(output_frames, input_frames, signal_length); 90 size_t phase = 0; 91 double max_correlation = correlation.at(0); 92 for (size_t i = 1; i < correlation.size(); i++) { 93 if (correlation.at(i) > max_correlation) { 94 max_correlation = correlation.at(i); 95 phase = i; 96 } 97 } 98 return phase; 99 } 100 101 std::vector<double> 102 normalize_frames(std::vector<double> & frames) 103 { 104 double max = abs( 105 *std::max_element(frames.begin(), frames.end(), 106 [](double a, double b) { return abs(a) < abs(b); })); 107 std::vector<double> normalized_frames; 108 normalized_frames.reserve(frames.size()); 109 for (const double frame : frames) { 110 normalized_frames.push_back(frame / max); 111 } 112 return normalized_frames; 113 } 114 115 /* heuristic comparison of aligned output and input signals, gets flaky if 116 * TONE_FREQUENCY is too high */ 117 void 118 compare_signals(std::vector<double> & output_frames, 119 std::vector<double> & input_frames) 120 { 121 ASSERT_EQ(output_frames.size(), input_frames.size()) 122 << "#Output frames != #input frames"; 123 size_t num_frames = output_frames.size(); 124 std::vector<double> normalized_output_frames = 125 normalize_frames(output_frames); 126 std::vector<double> normalized_input_frames = normalize_frames(input_frames); 127 128 /* calculate mean absolute errors */ 129 /* mean absolute errors between output and input */ 130 double io_mas = 0.0; 131 /* mean absolute errors between output and silence */ 132 double output_silence_mas = 0.0; 133 /* mean absolute errors between input and silence */ 134 double input_silence_mas = 0.0; 135 for (size_t i = 0; i < num_frames; i++) { 136 io_mas += 137 abs(normalized_output_frames.at(i) - normalized_input_frames.at(i)); 138 output_silence_mas += abs(normalized_output_frames.at(i)); 139 input_silence_mas += abs(normalized_input_frames.at(i)); 140 } 141 io_mas /= num_frames; 142 output_silence_mas /= num_frames; 143 input_silence_mas /= num_frames; 144 145 ASSERT_LT(io_mas, output_silence_mas) 146 << "Error between output and input should be less than output and " 147 "silence!"; 148 ASSERT_LT(io_mas, input_silence_mas) 149 << "Error between output and input should be less than output and " 150 "silence!"; 151 152 /* make sure extrema are in (roughly) correct location */ 153 /* number of maxima + minama expected in the frames*/ 154 const long NUM_EXTREMA = 155 2 * TONE_FREQUENCY * NUM_FRAMES_TO_OUTPUT / SAMPLE_FREQUENCY; 156 /* expected index of first maxima */ 157 const long FIRST_MAXIMUM_INDEX = SAMPLE_FREQUENCY / TONE_FREQUENCY / 4; 158 /* Threshold we expect all maxima and minima to be above or below. Ideally 159 the extrema would be 1 or -1, but particularly at the start of loopback 160 the values seen can be significantly lower. */ 161 const double THRESHOLD = 0.5; 162 163 for (size_t i = 0; i < NUM_EXTREMA; i++) { 164 bool is_maximum = i % 2 == 0; 165 /* expected offset to current extreme: i * stide between extrema */ 166 size_t offset = i * SAMPLE_FREQUENCY / TONE_FREQUENCY / 2; 167 if (is_maximum) { 168 ASSERT_GT(normalized_output_frames.at(FIRST_MAXIMUM_INDEX + offset), 169 THRESHOLD) 170 << "Output frames have unexpected missing maximum!"; 171 ASSERT_GT(normalized_input_frames.at(FIRST_MAXIMUM_INDEX + offset), 172 THRESHOLD) 173 << "Input frames have unexpected missing maximum!"; 174 } else { 175 ASSERT_LT(normalized_output_frames.at(FIRST_MAXIMUM_INDEX + offset), 176 -THRESHOLD) 177 << "Output frames have unexpected missing minimum!"; 178 ASSERT_LT(normalized_input_frames.at(FIRST_MAXIMUM_INDEX + offset), 179 -THRESHOLD) 180 << "Input frames have unexpected missing minimum!"; 181 } 182 } 183 } 184 185 struct user_state_loopback { 186 std::mutex user_state_mutex; 187 long position = 0; 188 /* track output */ 189 std::vector<double> output_frames; 190 /* track input */ 191 std::vector<double> input_frames; 192 }; 193 194 template <typename T> 195 long 196 data_cb_loop_duplex(cubeb_stream * stream, void * user, 197 const void * inputbuffer, void * outputbuffer, long nframes) 198 { 199 struct user_state_loopback * u = (struct user_state_loopback *)user; 200 T * ib = (T *)inputbuffer; 201 T * ob = (T *)outputbuffer; 202 203 if (stream == NULL || inputbuffer == NULL || outputbuffer == NULL) { 204 return CUBEB_ERROR; 205 } 206 207 std::lock_guard<std::mutex> lock(u->user_state_mutex); 208 /* generate our test tone on the fly */ 209 for (int i = 0; i < nframes; i++) { 210 double tone = 0.0; 211 if (u->position + i < NUM_FRAMES_TO_OUTPUT) { 212 /* generate sine wave */ 213 tone = 214 sin(2 * M_PI * (i + u->position) * TONE_FREQUENCY / SAMPLE_FREQUENCY); 215 tone *= OUTPUT_AMPLITUDE; 216 } 217 ob[i] = ConvertSampleToOutput<T>(tone); 218 u->output_frames.push_back(tone); 219 /* store any looped back output, may be silence */ 220 u->input_frames.push_back(ConvertSampleFromOutput(ib[i])); 221 } 222 223 u->position += nframes; 224 225 return nframes; 226 } 227 228 template <typename T> 229 long 230 data_cb_loop_input_only(cubeb_stream * stream, void * user, 231 const void * inputbuffer, void * outputbuffer, 232 long nframes) 233 { 234 struct user_state_loopback * u = (struct user_state_loopback *)user; 235 T * ib = (T *)inputbuffer; 236 237 if (outputbuffer != NULL) { 238 // Can't assert as it needs to return, so expect to fail instead 239 EXPECT_EQ(outputbuffer, (void *)NULL) 240 << "outputbuffer should be null in input only callback"; 241 return CUBEB_ERROR; 242 } 243 244 if (stream == NULL || inputbuffer == NULL) { 245 return CUBEB_ERROR; 246 } 247 248 std::lock_guard<std::mutex> lock(u->user_state_mutex); 249 for (int i = 0; i < nframes; i++) { 250 u->input_frames.push_back(ConvertSampleFromOutput(ib[i])); 251 } 252 253 return nframes; 254 } 255 256 template <typename T> 257 long 258 data_cb_playback(cubeb_stream * stream, void * user, const void * inputbuffer, 259 void * outputbuffer, long nframes) 260 { 261 struct user_state_loopback * u = (struct user_state_loopback *)user; 262 T * ob = (T *)outputbuffer; 263 264 if (stream == NULL || outputbuffer == NULL) { 265 return CUBEB_ERROR; 266 } 267 268 std::lock_guard<std::mutex> lock(u->user_state_mutex); 269 /* generate our test tone on the fly */ 270 for (int i = 0; i < nframes; i++) { 271 double tone = 0.0; 272 if (u->position + i < NUM_FRAMES_TO_OUTPUT) { 273 /* generate sine wave */ 274 tone = 275 sin(2 * M_PI * (i + u->position) * TONE_FREQUENCY / SAMPLE_FREQUENCY); 276 tone *= OUTPUT_AMPLITUDE; 277 } 278 ob[i] = ConvertSampleToOutput<T>(tone); 279 u->output_frames.push_back(tone); 280 } 281 282 u->position += nframes; 283 284 return nframes; 285 } 286 287 void 288 state_cb_loop(cubeb_stream * stream, void * /*user*/, cubeb_state state) 289 { 290 if (stream == NULL) 291 return; 292 293 switch (state) { 294 case CUBEB_STATE_STARTED: 295 fprintf(stderr, "stream started\n"); 296 break; 297 case CUBEB_STATE_STOPPED: 298 fprintf(stderr, "stream stopped\n"); 299 break; 300 case CUBEB_STATE_DRAINED: 301 fprintf(stderr, "stream drained\n"); 302 break; 303 default: 304 fprintf(stderr, "unknown stream state %d\n", state); 305 } 306 307 return; 308 } 309 310 void 311 run_loopback_duplex_test(bool is_float) 312 { 313 cubeb * ctx; 314 cubeb_stream * stream; 315 cubeb_stream_params input_params; 316 cubeb_stream_params output_params; 317 int r; 318 uint32_t latency_frames = 0; 319 320 r = common_init(&ctx, "Cubeb loopback example: duplex stream"); 321 ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library"; 322 323 std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit( 324 ctx, cubeb_destroy); 325 326 /* This test needs an available input device, skip it if this host does not 327 * have one. */ 328 if (!can_run_audio_input_test(ctx)) { 329 return; 330 } 331 332 input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE; 333 input_params.rate = SAMPLE_FREQUENCY; 334 input_params.channels = 1; 335 input_params.layout = CUBEB_LAYOUT_MONO; 336 input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK; 337 output_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE; 338 output_params.rate = SAMPLE_FREQUENCY; 339 output_params.channels = 1; 340 output_params.layout = CUBEB_LAYOUT_MONO; 341 output_params.prefs = CUBEB_STREAM_PREF_NONE; 342 343 std::unique_ptr<user_state_loopback> user_data(new user_state_loopback()); 344 ASSERT_TRUE(!!user_data) << "Error allocating user data"; 345 346 r = cubeb_get_min_latency(ctx, &output_params, &latency_frames); 347 ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency"; 348 349 /* setup a duplex stream with loopback */ 350 r = cubeb_stream_init(ctx, &stream, "Cubeb loopback", NULL, &input_params, 351 NULL, &output_params, latency_frames, 352 is_float ? data_cb_loop_duplex<float> 353 : data_cb_loop_duplex<short>, 354 state_cb_loop, user_data.get()); 355 ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream"; 356 357 std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)> 358 cleanup_stream_at_exit(stream, cubeb_stream_destroy); 359 360 cubeb_stream_start(stream); 361 delay(300); 362 cubeb_stream_stop(stream); 363 364 /* access after stop should not happen, but lock just in case and to appease 365 * sanitization tools */ 366 std::lock_guard<std::mutex> lock(user_data->user_state_mutex); 367 std::vector<double> & output_frames = user_data->output_frames; 368 std::vector<double> & input_frames = user_data->input_frames; 369 ASSERT_EQ(output_frames.size(), input_frames.size()) 370 << "#Output frames != #input frames"; 371 372 size_t phase = find_phase(user_data->output_frames, user_data->input_frames, 373 NUM_FRAMES_TO_OUTPUT); 374 375 /* extract vectors of just the relevant signal from output and input */ 376 auto output_frames_signal_start = output_frames.begin(); 377 auto output_frames_signal_end = output_frames.begin() + NUM_FRAMES_TO_OUTPUT; 378 std::vector<double> trimmed_output_frames(output_frames_signal_start, 379 output_frames_signal_end); 380 auto input_frames_signal_start = input_frames.begin() + phase; 381 auto input_frames_signal_end = 382 input_frames.begin() + phase + NUM_FRAMES_TO_OUTPUT; 383 std::vector<double> trimmed_input_frames(input_frames_signal_start, 384 input_frames_signal_end); 385 386 compare_signals(trimmed_output_frames, trimmed_input_frames); 387 } 388 389 TEST(cubeb, loopback_duplex) 390 { 391 run_loopback_duplex_test(true); 392 run_loopback_duplex_test(false); 393 } 394 395 void 396 run_loopback_separate_streams_test(bool is_float) 397 { 398 cubeb * ctx; 399 cubeb_stream * input_stream; 400 cubeb_stream * output_stream; 401 cubeb_stream_params input_params; 402 cubeb_stream_params output_params; 403 int r; 404 uint32_t latency_frames = 0; 405 406 r = common_init(&ctx, "Cubeb loopback example: separate streams"); 407 ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library"; 408 409 std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit( 410 ctx, cubeb_destroy); 411 412 if (!can_run_audio_input_test(ctx)) { 413 return; 414 } 415 416 input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE; 417 input_params.rate = SAMPLE_FREQUENCY; 418 input_params.channels = 1; 419 input_params.layout = CUBEB_LAYOUT_MONO; 420 input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK; 421 output_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE; 422 output_params.rate = SAMPLE_FREQUENCY; 423 output_params.channels = 1; 424 output_params.layout = CUBEB_LAYOUT_MONO; 425 output_params.prefs = CUBEB_STREAM_PREF_NONE; 426 427 std::unique_ptr<user_state_loopback> user_data(new user_state_loopback()); 428 ASSERT_TRUE(!!user_data) << "Error allocating user data"; 429 430 r = cubeb_get_min_latency(ctx, &output_params, &latency_frames); 431 ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency"; 432 433 /* setup an input stream with loopback */ 434 r = cubeb_stream_init(ctx, &input_stream, "Cubeb loopback input only", NULL, 435 &input_params, NULL, NULL, latency_frames, 436 is_float ? data_cb_loop_input_only<float> 437 : data_cb_loop_input_only<short>, 438 state_cb_loop, user_data.get()); 439 ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream"; 440 441 std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)> 442 cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy); 443 444 /* setup an output stream */ 445 r = cubeb_stream_init(ctx, &output_stream, "Cubeb loopback output only", NULL, 446 NULL, NULL, &output_params, latency_frames, 447 is_float ? data_cb_playback<float> 448 : data_cb_playback<short>, 449 state_cb_loop, user_data.get()); 450 ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream"; 451 452 std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)> 453 cleanup_output_stream_at_exit(output_stream, cubeb_stream_destroy); 454 455 cubeb_stream_start(input_stream); 456 cubeb_stream_start(output_stream); 457 delay(300); 458 cubeb_stream_stop(output_stream); 459 cubeb_stream_stop(input_stream); 460 461 /* access after stop should not happen, but lock just in case and to appease 462 * sanitization tools */ 463 std::lock_guard<std::mutex> lock(user_data->user_state_mutex); 464 std::vector<double> & output_frames = user_data->output_frames; 465 std::vector<double> & input_frames = user_data->input_frames; 466 ASSERT_LE(output_frames.size(), input_frames.size()) 467 << "#Output frames should be less or equal to #input frames"; 468 469 size_t phase = find_phase(user_data->output_frames, user_data->input_frames, 470 NUM_FRAMES_TO_OUTPUT); 471 472 /* extract vectors of just the relevant signal from output and input */ 473 auto output_frames_signal_start = output_frames.begin(); 474 auto output_frames_signal_end = output_frames.begin() + NUM_FRAMES_TO_OUTPUT; 475 std::vector<double> trimmed_output_frames(output_frames_signal_start, 476 output_frames_signal_end); 477 auto input_frames_signal_start = input_frames.begin() + phase; 478 auto input_frames_signal_end = 479 input_frames.begin() + phase + NUM_FRAMES_TO_OUTPUT; 480 std::vector<double> trimmed_input_frames(input_frames_signal_start, 481 input_frames_signal_end); 482 483 compare_signals(trimmed_output_frames, trimmed_input_frames); 484 } 485 486 TEST(cubeb, loopback_separate_streams) 487 { 488 run_loopback_separate_streams_test(true); 489 run_loopback_separate_streams_test(false); 490 } 491 492 void 493 run_loopback_silence_test(bool is_float) 494 { 495 cubeb * ctx; 496 cubeb_stream * input_stream; 497 cubeb_stream_params input_params; 498 int r; 499 uint32_t latency_frames = 0; 500 501 r = common_init(&ctx, "Cubeb loopback example: record silence"); 502 ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library"; 503 504 std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit( 505 ctx, cubeb_destroy); 506 507 if (!can_run_audio_input_test(ctx)) { 508 return; 509 } 510 511 input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE; 512 input_params.rate = SAMPLE_FREQUENCY; 513 input_params.channels = 1; 514 input_params.layout = CUBEB_LAYOUT_MONO; 515 input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK; 516 517 std::unique_ptr<user_state_loopback> user_data(new user_state_loopback()); 518 ASSERT_TRUE(!!user_data) << "Error allocating user data"; 519 520 r = cubeb_get_min_latency(ctx, &input_params, &latency_frames); 521 ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency"; 522 523 /* setup an input stream with loopback */ 524 r = cubeb_stream_init(ctx, &input_stream, "Cubeb loopback input only", NULL, 525 &input_params, NULL, NULL, latency_frames, 526 is_float ? data_cb_loop_input_only<float> 527 : data_cb_loop_input_only<short>, 528 state_cb_loop, user_data.get()); 529 ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream"; 530 531 std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)> 532 cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy); 533 534 cubeb_stream_start(input_stream); 535 delay(300); 536 cubeb_stream_stop(input_stream); 537 538 /* access after stop should not happen, but lock just in case and to appease 539 * sanitization tools */ 540 std::lock_guard<std::mutex> lock(user_data->user_state_mutex); 541 std::vector<double> & input_frames = user_data->input_frames; 542 543 /* expect to have at least ~50ms of frames */ 544 ASSERT_GE(input_frames.size(), SAMPLE_FREQUENCY / 20); 545 double EPISILON = 0.0001; 546 /* frames should be 0.0, but use epsilon to avoid possible issues with impls 547 that may use ~0.0 silence values. */ 548 for (double frame : input_frames) { 549 ASSERT_LT(abs(frame), EPISILON); 550 } 551 } 552 553 TEST(cubeb, loopback_silence) 554 { 555 run_loopback_silence_test(true); 556 run_loopback_silence_test(false); 557 } 558 559 void 560 run_loopback_device_selection_test(bool is_float) 561 { 562 cubeb * ctx; 563 cubeb_device_collection collection; 564 cubeb_stream * input_stream; 565 cubeb_stream * output_stream; 566 cubeb_stream_params input_params; 567 cubeb_stream_params output_params; 568 int r; 569 uint32_t latency_frames = 0; 570 571 r = common_init(&ctx, 572 "Cubeb loopback example: device selection, separate streams"); 573 ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library"; 574 575 std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit( 576 ctx, cubeb_destroy); 577 578 if (!can_run_audio_input_test(ctx)) { 579 return; 580 } 581 582 r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection); 583 if (r == CUBEB_ERROR_NOT_SUPPORTED) { 584 fprintf(stderr, "Device enumeration not supported" 585 " for this backend, skipping this test.\n"); 586 return; 587 } 588 589 ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r; 590 /* get first preferred output device id */ 591 std::string device_id; 592 for (size_t i = 0; i < collection.count; i++) { 593 if (collection.device[i].preferred) { 594 device_id = collection.device[i].device_id; 595 break; 596 } 597 } 598 cubeb_device_collection_destroy(ctx, &collection); 599 if (device_id.empty()) { 600 fprintf(stderr, "Could not find preferred device, aborting test.\n"); 601 return; 602 } 603 604 input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE; 605 input_params.rate = SAMPLE_FREQUENCY; 606 input_params.channels = 1; 607 input_params.layout = CUBEB_LAYOUT_MONO; 608 input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK; 609 output_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE; 610 output_params.rate = SAMPLE_FREQUENCY; 611 output_params.channels = 1; 612 output_params.layout = CUBEB_LAYOUT_MONO; 613 output_params.prefs = CUBEB_STREAM_PREF_NONE; 614 615 std::unique_ptr<user_state_loopback> user_data(new user_state_loopback()); 616 ASSERT_TRUE(!!user_data) << "Error allocating user data"; 617 618 r = cubeb_get_min_latency(ctx, &output_params, &latency_frames); 619 ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency"; 620 621 /* setup an input stream with loopback */ 622 r = cubeb_stream_init(ctx, &input_stream, "Cubeb loopback input only", 623 device_id.c_str(), &input_params, NULL, NULL, 624 latency_frames, 625 is_float ? data_cb_loop_input_only<float> 626 : data_cb_loop_input_only<short>, 627 state_cb_loop, user_data.get()); 628 ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream"; 629 630 std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)> 631 cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy); 632 633 /* setup an output stream */ 634 r = cubeb_stream_init(ctx, &output_stream, "Cubeb loopback output only", NULL, 635 NULL, device_id.c_str(), &output_params, latency_frames, 636 is_float ? data_cb_playback<float> 637 : data_cb_playback<short>, 638 state_cb_loop, user_data.get()); 639 ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream"; 640 641 std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)> 642 cleanup_output_stream_at_exit(output_stream, cubeb_stream_destroy); 643 644 cubeb_stream_start(input_stream); 645 cubeb_stream_start(output_stream); 646 delay(300); 647 cubeb_stream_stop(output_stream); 648 cubeb_stream_stop(input_stream); 649 650 /* access after stop should not happen, but lock just in case and to appease 651 * sanitization tools */ 652 std::lock_guard<std::mutex> lock(user_data->user_state_mutex); 653 std::vector<double> & output_frames = user_data->output_frames; 654 std::vector<double> & input_frames = user_data->input_frames; 655 ASSERT_LE(output_frames.size(), input_frames.size()) 656 << "#Output frames should be less or equal to #input frames"; 657 658 size_t phase = find_phase(user_data->output_frames, user_data->input_frames, 659 NUM_FRAMES_TO_OUTPUT); 660 661 /* extract vectors of just the relevant signal from output and input */ 662 auto output_frames_signal_start = output_frames.begin(); 663 auto output_frames_signal_end = output_frames.begin() + NUM_FRAMES_TO_OUTPUT; 664 std::vector<double> trimmed_output_frames(output_frames_signal_start, 665 output_frames_signal_end); 666 auto input_frames_signal_start = input_frames.begin() + phase; 667 auto input_frames_signal_end = 668 input_frames.begin() + phase + NUM_FRAMES_TO_OUTPUT; 669 std::vector<double> trimmed_input_frames(input_frames_signal_start, 670 input_frames_signal_end); 671 672 compare_signals(trimmed_output_frames, trimmed_input_frames); 673 } 674 675 TEST(cubeb, loopback_device_selection) 676 { 677 run_loopback_device_selection_test(true); 678 run_loopback_device_selection_test(false); 679 }