tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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