Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/media/libcubeb/gtest/test_resampler.cpp
Line
Count
Source (jump to first uncovered line)
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
#ifndef NOMINMAX
8
#define NOMINMAX
9
#endif // NOMINMAX
10
#include "gtest/gtest.h"
11
#include "common.h"
12
#include "cubeb_resampler_internal.h"
13
#include <stdio.h>
14
#include <algorithm>
15
#include <iostream>
16
17
/* Windows cmath USE_MATH_DEFINE thing... */
18
const float PI = 3.14159265359f;
19
20
/* Testing all sample rates is very long, so if THOROUGH_TESTING is not defined,
21
 * only part of the test suite is ran. */
22
#ifdef THOROUGH_TESTING
23
/* Some standard sample rates we're testing with. */
24
const uint32_t sample_rates[] = {
25
    8000,
26
   16000,
27
   32000,
28
   44100,
29
   48000,
30
   88200,
31
   96000,
32
  192000
33
};
34
/* The maximum number of channels we're resampling. */
35
const uint32_t max_channels = 2;
36
/* The minimum an maximum number of milliseconds we're resampling for. This is
37
 * used to simulate the fact that the audio stream is resampled in chunks,
38
 * because audio is delivered using callbacks. */
39
const uint32_t min_chunks = 10; /* ms */
40
const uint32_t max_chunks = 30; /* ms */
41
const uint32_t chunk_increment = 1;
42
43
#else
44
45
const uint32_t sample_rates[] = {
46
    8000,
47
   44100,
48
   48000,
49
};
50
const uint32_t max_channels = 2;
51
const uint32_t min_chunks = 10; /* ms */
52
const uint32_t max_chunks = 30; /* ms */
53
const uint32_t chunk_increment = 10;
54
#endif
55
56
#define DUMP_ARRAYS
57
#ifdef DUMP_ARRAYS
58
/**
59
 * Files produced by dump(...) can be converted to .wave files using:
60
 *
61
 * sox -c <channel_count> -r <rate> -e float -b 32  file.raw file.wav
62
 *
63
 * for floating-point audio, or:
64
 *
65
 * sox -c <channel_count> -r <rate> -e unsigned -b 16  file.raw file.wav
66
 *
67
 * for 16bit integer audio.
68
 */
69
70
/* Use the correct implementation of fopen, depending on the platform. */
71
void fopen_portable(FILE ** f, const char * name, const char * mode)
72
0
{
73
#ifdef WIN32
74
  fopen_s(f, name, mode);
75
#else
76
  *f = fopen(name, mode);
77
0
#endif
78
0
}
79
80
template<typename T>
81
void dump(const char * name, T * frames, size_t count)
82
0
{
83
0
  FILE * file;
84
0
  fopen_portable(&file, name, "wb");
85
0
86
0
  if (!file) {
87
0
    fprintf(stderr, "error opening %s\n", name);
88
0
    return;
89
0
  }
90
0
91
0
  if (count != fwrite(frames, sizeof(T), count, file)) {
92
0
    fprintf(stderr, "error writing to %s\n", name);
93
0
  }
94
0
  fclose(file);
95
0
}
96
#else
97
template<typename T>
98
void dump(const char * name, T * frames, size_t count)
99
{ }
100
#endif
101
102
// The more the ratio is far from 1, the more we accept a big error.
103
float epsilon_tweak_ratio(float ratio)
104
0
{
105
0
  return ratio >= 1 ? ratio : 1 / ratio;
106
0
}
107
108
// Epsilon values for comparing resampled data to expected data.
109
// The bigger the resampling ratio is, the more lax we are about errors.
110
template<typename T>
111
T epsilon(float ratio);
112
113
template<>
114
0
float epsilon(float ratio) {
115
0
  return 0.08f * epsilon_tweak_ratio(ratio);
116
0
}
117
118
template<>
119
0
int16_t epsilon(float ratio) {
120
0
  return static_cast<int16_t>(10 * epsilon_tweak_ratio(ratio));
121
0
}
122
123
void test_delay_lines(uint32_t delay_frames, uint32_t channels, uint32_t chunk_ms)
124
0
{
125
0
  const size_t length_s = 2;
126
0
  const size_t rate = 44100;
127
0
  const size_t length_frames = rate * length_s;
128
0
  delay_line<float> delay(delay_frames, channels, rate);
129
0
  auto_array<float> input;
130
0
  auto_array<float> output;
131
0
  uint32_t chunk_length = channels * chunk_ms * rate / 1000;
132
0
  uint32_t output_offset = 0;
133
0
  uint32_t channel = 0;
134
0
135
0
  /** Generate diracs every 100 frames, and check they are delayed. */
136
0
  input.push_silence(length_frames * channels);
137
0
  for (uint32_t i = 0; i < input.length() - 1; i+=100) {
138
0
    input.data()[i + channel] = 0.5;
139
0
    channel = (channel + 1) % channels;
140
0
  }
141
0
  dump("input.raw", input.data(), input.length());
142
0
  while(input.length()) {
143
0
    uint32_t to_pop = std::min<uint32_t>(input.length(), chunk_length * channels);
144
0
    float * in = delay.input_buffer(to_pop / channels);
145
0
    input.pop(in, to_pop);
146
0
    delay.written(to_pop / channels);
147
0
    output.push_silence(to_pop);
148
0
    delay.output(output.data() + output_offset, to_pop / channels);
149
0
    output_offset += to_pop;
150
0
  }
151
0
152
0
  // Check the diracs have been shifted by `delay_frames` frames.
153
0
  for (uint32_t i = 0; i < output.length() - delay_frames * channels + 1; i+=100) {
154
0
    ASSERT_EQ(output.data()[i + channel + delay_frames * channels], 0.5);
155
0
    channel = (channel + 1) % channels;
156
0
  }
157
0
158
0
  dump("output.raw", output.data(), output.length());
159
0
}
160
/**
161
 * This takes sine waves with a certain `channels` count, `source_rate`, and
162
 * resample them, by chunk of `chunk_duration` milliseconds, to `target_rate`.
163
 * Then a sample-wise comparison is performed against a sine wave generated at
164
 * the correct rate.
165
 */
166
template<typename T>
167
void test_resampler_one_way(uint32_t channels, uint32_t source_rate, uint32_t target_rate, float chunk_duration)
168
0
{
169
0
  size_t chunk_duration_in_source_frames = static_cast<uint32_t>(ceil(chunk_duration * source_rate / 1000.));
170
0
  float resampling_ratio = static_cast<float>(source_rate) / target_rate;
171
0
  cubeb_resampler_speex_one_way<T> resampler(channels, source_rate, target_rate, 3);
172
0
  auto_array<T> source(channels * source_rate * 10);
173
0
  auto_array<T> destination(channels * target_rate * 10);
174
0
  auto_array<T> expected(channels * target_rate * 10);
175
0
  uint32_t phase_index = 0;
176
0
  uint32_t offset = 0;
177
0
  const uint32_t buf_len = 2; /* seconds */
178
0
179
0
  // generate a sine wave in each channel, at the source sample rate
180
0
  source.push_silence(channels * source_rate * buf_len);
181
0
  while(offset != source.length()) {
182
0
    float  p = phase_index++ / static_cast<float>(source_rate);
183
0
    for (uint32_t j = 0; j < channels; j++) {
184
0
      source.data()[offset++] = 0.5 * sin(440. * 2 * PI * p);
185
0
    }
186
0
  }
187
0
188
0
  dump("input.raw", source.data(), source.length());
189
0
190
0
  expected.push_silence(channels * target_rate * buf_len);
191
0
  // generate a sine wave in each channel, at the target sample rate.
192
0
  // Insert silent samples at the beginning to account for the resampler latency.
193
0
  offset = resampler.latency() * channels;
194
0
  for (uint32_t i = 0; i < offset; i++) {
195
0
    expected.data()[i] = 0.0f;
196
0
  }
197
0
  phase_index = 0;
198
0
  while (offset != expected.length()) {
199
0
    float  p = phase_index++ / static_cast<float>(target_rate);
200
0
    for (uint32_t j = 0; j < channels; j++) {
201
0
      expected.data()[offset++] = 0.5 * sin(440. * 2 * PI * p);
202
0
    }
203
0
  }
204
0
205
0
  dump("expected.raw", expected.data(), expected.length());
206
0
207
0
  // resample by chunk
208
0
  uint32_t write_offset = 0;
209
0
  destination.push_silence(channels * target_rate * buf_len);
210
0
  while (write_offset < destination.length())
211
0
  {
212
0
    size_t output_frames = static_cast<uint32_t>(floor(chunk_duration_in_source_frames / resampling_ratio));
213
0
    uint32_t input_frames = resampler.input_needed_for_output(output_frames);
214
0
    resampler.input(source.data(), input_frames);
215
0
    source.pop(nullptr, input_frames * channels);
216
0
    resampler.output(destination.data() + write_offset,
217
0
                     std::min(output_frames, (destination.length() - write_offset) / channels));
218
0
    write_offset += output_frames * channels;
219
0
  }
220
0
221
0
  dump("output.raw", destination.data(), expected.length());
222
0
223
0
  // compare, taking the latency into account
224
0
  bool fuzzy_equal = true;
225
0
  for (uint32_t i = resampler.latency() + 1; i < expected.length(); i++) {
226
0
    float diff = fabs(expected.data()[i] - destination.data()[i]);
227
0
    if (diff > epsilon<T>(resampling_ratio)) {
228
0
      fprintf(stderr, "divergence at %d: %f %f (delta %f)\n", i, expected.data()[i], destination.data()[i], diff);
229
0
      fuzzy_equal = false;
230
0
    }
231
0
  }
232
0
  ASSERT_TRUE(fuzzy_equal);
233
0
}
234
235
template<typename T>
236
cubeb_sample_format cubeb_format();
237
238
template<>
239
cubeb_sample_format cubeb_format<float>()
240
0
{
241
0
  return CUBEB_SAMPLE_FLOAT32NE;
242
0
}
243
244
template<>
245
cubeb_sample_format cubeb_format<short>()
246
0
{
247
0
  return CUBEB_SAMPLE_S16NE;
248
0
}
249
250
struct osc_state {
251
  osc_state()
252
    : input_phase_index(0)
253
    , output_phase_index(0)
254
    , output_offset(0)
255
    , input_channels(0)
256
    , output_channels(0)
257
0
  {}
258
  uint32_t input_phase_index;
259
  uint32_t max_output_phase_index;
260
  uint32_t output_phase_index;
261
  uint32_t output_offset;
262
  uint32_t input_channels;
263
  uint32_t output_channels;
264
  uint32_t output_rate;
265
  uint32_t target_rate;
266
  auto_array<float> input;
267
  auto_array<float> output;
268
};
269
270
uint32_t fill_with_sine(float * buf, uint32_t rate, uint32_t channels,
271
                        uint32_t frames, uint32_t initial_phase)
272
0
{
273
0
  uint32_t offset = 0;
274
0
  for (uint32_t i = 0; i < frames; i++) {
275
0
    float  p = initial_phase++ / static_cast<float>(rate);
276
0
    for (uint32_t j = 0; j < channels; j++) {
277
0
      buf[offset++] = 0.5 * sin(440. * 2 * PI * p);
278
0
    }
279
0
  }
280
0
  return initial_phase;
281
0
}
282
283
long data_cb_resampler(cubeb_stream * /*stm*/, void * user_ptr,
284
             const void * input_buffer, void * output_buffer, long frame_count)
285
0
{
286
0
  osc_state * state = reinterpret_cast<osc_state*>(user_ptr);
287
0
  const float * in = reinterpret_cast<const float*>(input_buffer);
288
0
  float * out = reinterpret_cast<float*>(output_buffer);
289
0
290
0
  state->input.push(in, frame_count * state->input_channels);
291
0
292
0
  /* Check how much output frames we need to write */
293
0
  uint32_t remaining = state->max_output_phase_index - state->output_phase_index;
294
0
  uint32_t to_write = std::min<uint32_t>(remaining, frame_count);
295
0
  state->output_phase_index = fill_with_sine(out,
296
0
                                             state->target_rate,
297
0
                                             state->output_channels,
298
0
                                             to_write,
299
0
                                             state->output_phase_index);
300
0
301
0
  return to_write;
302
0
}
303
304
template<typename T>
305
bool array_fuzzy_equal(const auto_array<T>& lhs, const auto_array<T>& rhs, T epsi)
306
{
307
  uint32_t len = std::min(lhs.length(), rhs.length());
308
309
  for (uint32_t i = 0; i < len; i++) {
310
    if (fabs(lhs.at(i) - rhs.at(i)) > epsi) {
311
      std::cout << "not fuzzy equal at index: " << i
312
                << " lhs: " << lhs.at(i) <<  " rhs: " << rhs.at(i)
313
                << " delta: " << fabs(lhs.at(i) - rhs.at(i))
314
                << " epsilon: "<< epsi << std::endl;
315
      return false;
316
    }
317
  }
318
  return true;
319
}
320
321
template<typename T>
322
void test_resampler_duplex(uint32_t input_channels, uint32_t output_channels,
323
                           uint32_t input_rate, uint32_t output_rate,
324
                           uint32_t target_rate, float chunk_duration)
325
0
{
326
0
  cubeb_stream_params input_params;
327
0
  cubeb_stream_params output_params;
328
0
  osc_state state;
329
0
330
0
  input_params.format = output_params.format = cubeb_format<T>();
331
0
  state.input_channels = input_params.channels = input_channels;
332
0
  state.output_channels = output_params.channels = output_channels;
333
0
  input_params.rate = input_rate;
334
0
  state.output_rate = output_params.rate = output_rate;
335
0
  state.target_rate = target_rate;
336
0
  input_params.prefs = output_params.prefs = CUBEB_STREAM_PREF_NONE;
337
0
  long got;
338
0
339
0
  cubeb_resampler * resampler =
340
0
    cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params, target_rate,
341
0
                           data_cb_resampler, (void*)&state, CUBEB_RESAMPLER_QUALITY_VOIP);
342
0
343
0
  long latency = cubeb_resampler_latency(resampler);
344
0
345
0
  const uint32_t duration_s = 2;
346
0
  int32_t duration_frames = duration_s * target_rate;
347
0
  uint32_t input_array_frame_count = ceil(chunk_duration * input_rate / 1000) + ceilf(static_cast<float>(input_rate) / target_rate) * 2;
348
0
  uint32_t output_array_frame_count = chunk_duration * output_rate / 1000;
349
0
  auto_array<float> input_buffer(input_channels * input_array_frame_count);
350
0
  auto_array<float> output_buffer(output_channels * output_array_frame_count);
351
0
  auto_array<float> expected_resampled_input(input_channels * duration_frames);
352
0
  auto_array<float> expected_resampled_output(output_channels * output_rate * duration_s);
353
0
354
0
  state.max_output_phase_index = duration_s * target_rate;
355
0
356
0
  expected_resampled_input.push_silence(input_channels * duration_frames);
357
0
  expected_resampled_output.push_silence(output_channels * output_rate * duration_s);
358
0
359
0
  /* expected output is a 440Hz sine wave at 16kHz */
360
0
  fill_with_sine(expected_resampled_input.data() + latency,
361
0
                 target_rate, input_channels, duration_frames - latency, 0);
362
0
  /* expected output is a 440Hz sine wave at 32kHz */
363
0
  fill_with_sine(expected_resampled_output.data() + latency,
364
0
                 output_rate, output_channels, output_rate * duration_s - latency, 0);
365
0
366
0
  while (state.output_phase_index != state.max_output_phase_index) {
367
0
    uint32_t leftover_samples = input_buffer.length() * input_channels;
368
0
    input_buffer.reserve(input_array_frame_count);
369
0
    state.input_phase_index = fill_with_sine(input_buffer.data() + leftover_samples,
370
0
                                             input_rate,
371
0
                                             input_channels,
372
0
                                             input_array_frame_count - leftover_samples,
373
0
                                             state.input_phase_index);
374
0
    long input_consumed = input_array_frame_count;
375
0
    input_buffer.set_length(input_array_frame_count);
376
0
377
0
    got = cubeb_resampler_fill(resampler,
378
0
                               input_buffer.data(), &input_consumed,
379
0
                               output_buffer.data(), output_array_frame_count);
380
0
381
0
    /* handle leftover input */
382
0
    if (input_array_frame_count != static_cast<uint32_t>(input_consumed)) {
383
0
      input_buffer.pop(nullptr, input_consumed * input_channels);
384
0
    } else {
385
0
      input_buffer.clear();
386
0
    }
387
0
388
0
    state.output.push(output_buffer.data(), got * state.output_channels);
389
0
  }
390
0
391
0
  dump("input_expected.raw", expected_resampled_input.data(), expected_resampled_input.length());
392
0
  dump("output_expected.raw", expected_resampled_output.data(), expected_resampled_output.length());
393
0
  dump("input.raw", state.input.data(), state.input.length());
394
0
  dump("output.raw", state.output.data(), state.output.length());
395
0
396
0
 // This is disabled because the latency estimation in the resampler code is
397
0
 // slightly off so we can generate expected vectors.
398
0
 // See https://github.com/kinetiknz/cubeb/issues/93
399
0
 // ASSERT_TRUE(array_fuzzy_equal(state.input, expected_resampled_input, epsilon<T>(input_rate/target_rate)));
400
0
 // ASSERT_TRUE(array_fuzzy_equal(state.output, expected_resampled_output, epsilon<T>(output_rate/target_rate)));
401
0
402
0
  cubeb_resampler_destroy(resampler);
403
0
}
404
405
0
#define array_size(x) (sizeof(x) / sizeof(x[0]))
406
407
TEST(cubeb, resampler_one_way)
408
0
{
409
0
  /* Test one way resamplers */
410
0
  for (uint32_t channels = 1; channels <= max_channels; channels++) {
411
0
    for (uint32_t source_rate = 0; source_rate < array_size(sample_rates); source_rate++) {
412
0
      for (uint32_t dest_rate = 0; dest_rate < array_size(sample_rates); dest_rate++) {
413
0
        for (uint32_t chunk_duration = min_chunks; chunk_duration < max_chunks; chunk_duration+=chunk_increment) {
414
0
          fprintf(stderr, "one_way: channels: %d, source_rate: %d, dest_rate: %d, chunk_duration: %d\n",
415
0
                  channels, sample_rates[source_rate], sample_rates[dest_rate], chunk_duration);
416
0
          test_resampler_one_way<float>(channels, sample_rates[source_rate],
417
0
                                        sample_rates[dest_rate], chunk_duration);
418
0
        }
419
0
      }
420
0
    }
421
0
  }
422
0
}
423
424
TEST(cubeb, DISABLED_resampler_duplex)
425
0
{
426
0
  for (uint32_t input_channels = 1; input_channels <= max_channels; input_channels++) {
427
0
    for (uint32_t output_channels = 1; output_channels <= max_channels; output_channels++) {
428
0
      for (uint32_t source_rate_input = 0; source_rate_input < array_size(sample_rates); source_rate_input++) {
429
0
        for (uint32_t source_rate_output = 0; source_rate_output < array_size(sample_rates); source_rate_output++) {
430
0
          for (uint32_t dest_rate = 0; dest_rate < array_size(sample_rates); dest_rate++) {
431
0
            for (uint32_t chunk_duration = min_chunks; chunk_duration < max_chunks; chunk_duration+=chunk_increment) {
432
0
              fprintf(stderr, "input channels:%d output_channels:%d input_rate:%d "
433
0
                     "output_rate:%d target_rate:%d chunk_ms:%d\n",
434
0
                     input_channels, output_channels,
435
0
                     sample_rates[source_rate_input],
436
0
                     sample_rates[source_rate_output],
437
0
                     sample_rates[dest_rate],
438
0
                     chunk_duration);
439
0
              test_resampler_duplex<float>(input_channels, output_channels,
440
0
                                           sample_rates[source_rate_input],
441
0
                                           sample_rates[source_rate_output],
442
0
                                           sample_rates[dest_rate],
443
0
                                           chunk_duration);
444
0
            }
445
0
          }
446
0
        }
447
0
      }
448
0
    }
449
0
  }
450
0
}
451
452
TEST(cubeb, resampler_delay_line)
453
0
{
454
0
  for (uint32_t channel = 1; channel <= 2; channel++) {
455
0
    for (uint32_t delay_frames = 4; delay_frames <= 40; delay_frames+=chunk_increment) {
456
0
      for (uint32_t chunk_size = 10; chunk_size <= 30; chunk_size++) {
457
0
       fprintf(stderr, "channel: %d, delay_frames: %d, chunk_size: %d\n",
458
0
              channel, delay_frames, chunk_size);
459
0
        test_delay_lines(delay_frames, channel, chunk_size);
460
0
      }
461
0
    }
462
0
  }
463
0
}
464
465
long test_output_only_noop_data_cb(cubeb_stream * /*stm*/, void * /*user_ptr*/,
466
                                   const void * input_buffer,
467
                                   void * output_buffer, long frame_count)
468
0
{
469
0
  EXPECT_TRUE(output_buffer);
470
0
  EXPECT_TRUE(!input_buffer);
471
0
  return frame_count;
472
0
}
473
474
TEST(cubeb, resampler_output_only_noop)
475
0
{
476
0
  cubeb_stream_params output_params;
477
0
  int target_rate;
478
0
479
0
  output_params.rate = 44100;
480
0
  output_params.channels = 1;
481
0
  output_params.format = CUBEB_SAMPLE_FLOAT32NE;
482
0
  target_rate = output_params.rate;
483
0
484
0
  cubeb_resampler * resampler =
485
0
    cubeb_resampler_create((cubeb_stream*)nullptr, nullptr, &output_params, target_rate,
486
0
                           test_output_only_noop_data_cb, nullptr,
487
0
                           CUBEB_RESAMPLER_QUALITY_VOIP);
488
0
489
0
  const long out_frames = 128;
490
0
  float out_buffer[out_frames];
491
0
  long got;
492
0
493
0
  got = cubeb_resampler_fill(resampler, nullptr, nullptr,
494
0
                             out_buffer, out_frames);
495
0
496
0
  ASSERT_EQ(got, out_frames);
497
0
498
0
  cubeb_resampler_destroy(resampler);
499
0
}
500
501
long test_drain_data_cb(cubeb_stream * /*stm*/, void * user_ptr,
502
                        const void * input_buffer,
503
                        void * output_buffer, long frame_count)
504
0
{
505
0
  EXPECT_TRUE(output_buffer);
506
0
  EXPECT_TRUE(!input_buffer);
507
0
  auto cb_count = static_cast<int *>(user_ptr);
508
0
  (*cb_count)++;
509
0
  return frame_count - 1;
510
0
}
511
512
TEST(cubeb, resampler_drain)
513
0
{
514
0
  cubeb_stream_params output_params;
515
0
  int target_rate;
516
0
517
0
  output_params.rate = 44100;
518
0
  output_params.channels = 1;
519
0
  output_params.format = CUBEB_SAMPLE_FLOAT32NE;
520
0
  target_rate = 48000;
521
0
  int cb_count = 0;
522
0
523
0
  cubeb_resampler * resampler =
524
0
    cubeb_resampler_create((cubeb_stream*)nullptr, nullptr, &output_params, target_rate,
525
0
                           test_drain_data_cb, &cb_count,
526
0
                           CUBEB_RESAMPLER_QUALITY_VOIP);
527
0
528
0
  const long out_frames = 128;
529
0
  float out_buffer[out_frames];
530
0
  long got;
531
0
532
0
  do {
533
0
    got = cubeb_resampler_fill(resampler, nullptr, nullptr,
534
0
                               out_buffer, out_frames);
535
0
  } while (got == out_frames);
536
0
537
0
  /* The callback should be called once but not again after returning <
538
0
   * frame_count. */
539
0
  ASSERT_EQ(cb_count, 1);
540
0
541
0
  cubeb_resampler_destroy(resampler);
542
0
}
543
544
// gtest does not support using ASSERT_EQ and friend in a function that returns
545
// a value.
546
void check_output(const void * input_buffer, void * output_buffer, long frame_count)
547
0
{
548
0
  ASSERT_EQ(input_buffer, nullptr);
549
0
  ASSERT_EQ(frame_count, 256);
550
0
  ASSERT_TRUE(!!output_buffer);
551
0
}
552
553
long cb_passthrough_resampler_output(cubeb_stream * /*stm*/, void * /*user_ptr*/,
554
                                     const void * input_buffer,
555
                                     void * output_buffer, long frame_count)
556
0
{
557
0
  check_output(input_buffer, output_buffer, frame_count);
558
0
  return frame_count;
559
0
}
560
561
TEST(cubeb, resampler_passthrough_output_only)
562
0
{
563
0
  // Test that the passthrough resampler works when there is only an output stream.
564
0
  cubeb_stream_params output_params;
565
0
566
0
  const size_t output_channels = 2;
567
0
  output_params.channels = output_channels;
568
0
  output_params.rate = 44100;
569
0
  output_params.format = CUBEB_SAMPLE_FLOAT32NE;
570
0
  int target_rate = output_params.rate;
571
0
572
0
  cubeb_resampler * resampler =
573
0
    cubeb_resampler_create((cubeb_stream*)nullptr, nullptr, &output_params,
574
0
                           target_rate, cb_passthrough_resampler_output, nullptr,
575
0
                           CUBEB_RESAMPLER_QUALITY_VOIP);
576
0
577
0
  float output_buffer[output_channels * 256];
578
0
579
0
  long got;
580
0
  for (uint32_t i = 0; i < 30; i++) {
581
0
    got = cubeb_resampler_fill(resampler, nullptr, nullptr, output_buffer, 256);
582
0
    ASSERT_EQ(got, 256);
583
0
  }
584
0
585
0
  cubeb_resampler_destroy(resampler);
586
0
}
587
588
// gtest does not support using ASSERT_EQ and friend in a function that returns
589
// a value.
590
void check_input(const void * input_buffer, void * output_buffer, long frame_count)
591
0
{
592
0
  ASSERT_EQ(output_buffer, nullptr);
593
0
  ASSERT_EQ(frame_count, 256);
594
0
  ASSERT_TRUE(!!input_buffer);
595
0
}
596
597
long cb_passthrough_resampler_input(cubeb_stream * /*stm*/, void * /*user_ptr*/,
598
                                    const void * input_buffer,
599
                                    void * output_buffer, long frame_count)
600
0
{
601
0
  check_input(input_buffer, output_buffer, frame_count);
602
0
  return frame_count;
603
0
}
604
605
TEST(cubeb, resampler_passthrough_input_only)
606
0
{
607
0
  // Test that the passthrough resampler works when there is only an output stream.
608
0
  cubeb_stream_params input_params;
609
0
610
0
  const size_t input_channels = 2;
611
0
  input_params.channels = input_channels;
612
0
  input_params.rate = 44100;
613
0
  input_params.format = CUBEB_SAMPLE_FLOAT32NE;
614
0
  int target_rate = input_params.rate;
615
0
616
0
  cubeb_resampler * resampler =
617
0
    cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, nullptr,
618
0
                           target_rate, cb_passthrough_resampler_input, nullptr,
619
0
                           CUBEB_RESAMPLER_QUALITY_VOIP);
620
0
621
0
  float input_buffer[input_channels * 256];
622
0
623
0
  long got;
624
0
  for (uint32_t i = 0; i < 30; i++) {
625
0
    long int frames = 256;
626
0
    got = cubeb_resampler_fill(resampler, input_buffer, &frames, nullptr, 0);
627
0
    ASSERT_EQ(got, 256);
628
0
  }
629
0
630
0
  cubeb_resampler_destroy(resampler);
631
0
}
632
633
template<typename T>
634
long seq(T* array, int stride, long start, long count)
635
0
{
636
0
  uint32_t output_idx = 0;
637
0
  for(int i = 0; i < count; i++) {
638
0
    for (int j = 0; j < stride; j++) {
639
0
      array[output_idx + j] = static_cast<T>(start + i);
640
0
    }
641
0
    output_idx += stride;
642
0
  }
643
0
  return start + count;
644
0
}
645
646
template<typename T>
647
void is_seq(T * array, int stride, long count, long expected_start)
648
0
{
649
0
  uint32_t output_index = 0;
650
0
  for (long i = 0; i < count; i++) {
651
0
    for (int j = 0; j < stride; j++) {
652
0
      ASSERT_EQ(array[output_index + j], expected_start + i);
653
0
    }
654
0
    output_index += stride;
655
0
  }
656
0
}
657
658
template<typename T>
659
void is_not_seq(T * array, int stride, long count, long expected_start)
660
0
{
661
0
  uint32_t output_index = 0;
662
0
  for (long i = 0; i < count; i++) {
663
0
    for (int j = 0; j < stride; j++) {
664
0
      ASSERT_NE(array[output_index + j], expected_start + i);
665
0
    }
666
0
    output_index += stride;
667
0
  }
668
0
}
669
670
struct closure {
671
  int input_channel_count;
672
};
673
674
// gtest does not support using ASSERT_EQ and friend in a function that returns
675
// a value.
676
template<typename T>
677
void check_duplex(const T * input_buffer,
678
                  T * output_buffer, long frame_count,
679
                  int input_channel_count)
680
0
{
681
0
  ASSERT_EQ(frame_count, 256);
682
0
  // Silence scan-build warning.
683
0
  ASSERT_TRUE(!!output_buffer); assert(output_buffer);
684
0
  ASSERT_TRUE(!!input_buffer); assert(input_buffer);
685
0
686
0
  int output_index = 0;
687
0
  int input_index = 0;
688
0
  for (int i = 0; i < frame_count; i++) {
689
0
    // output is two channels, input one or two channels.
690
0
    if (input_channel_count == 1) {
691
0
      output_buffer[output_index] = output_buffer[output_index + 1] = input_buffer[i];
692
0
    } else if (input_channel_count == 2) {
693
0
      output_buffer[output_index] = input_buffer[input_index];
694
0
      output_buffer[output_index + 1] = input_buffer[input_index + 1];
695
0
    }
696
0
    output_index += 2;
697
0
    input_index += input_channel_count;
698
0
  }
699
0
}
700
701
long cb_passthrough_resampler_duplex(cubeb_stream * /*stm*/, void * user_ptr,
702
                                     const void * input_buffer,
703
                                     void * output_buffer, long frame_count)
704
0
{
705
0
  closure * c = reinterpret_cast<closure*>(user_ptr);
706
0
  check_duplex<float>(static_cast<const float*>(input_buffer),
707
0
                      static_cast<float*>(output_buffer),
708
0
                      frame_count, c->input_channel_count);
709
0
  return frame_count;
710
0
}
711
712
713
TEST(cubeb, resampler_passthrough_duplex_callback_reordering)
714
0
{
715
0
  // Test that when pre-buffering on resampler creation, we can survive an input
716
0
  // callback being delayed.
717
0
718
0
  cubeb_stream_params input_params;
719
0
  cubeb_stream_params output_params;
720
0
721
0
  const int input_channels = 1;
722
0
  const int output_channels = 2;
723
0
724
0
  input_params.channels = input_channels;
725
0
  input_params.rate = 44100;
726
0
  input_params.format = CUBEB_SAMPLE_FLOAT32NE;
727
0
728
0
  output_params.channels = output_channels;
729
0
  output_params.rate = input_params.rate;
730
0
  output_params.format = CUBEB_SAMPLE_FLOAT32NE;
731
0
732
0
  int target_rate = input_params.rate;
733
0
734
0
  closure c;
735
0
  c.input_channel_count = input_channels;
736
0
737
0
  cubeb_resampler * resampler =
738
0
    cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params,
739
0
                           target_rate, cb_passthrough_resampler_duplex, &c,
740
0
                           CUBEB_RESAMPLER_QUALITY_VOIP);
741
0
742
0
  const long BUF_BASE_SIZE = 256;
743
0
  float input_buffer_prebuffer[input_channels * BUF_BASE_SIZE * 2];
744
0
  float input_buffer_glitch[input_channels * BUF_BASE_SIZE * 2];
745
0
  float input_buffer_normal[input_channels * BUF_BASE_SIZE];
746
0
  float output_buffer[output_channels * BUF_BASE_SIZE];
747
0
748
0
  long seq_idx = 0;
749
0
  long output_seq_idx = 0;
750
0
751
0
  long prebuffer_frames = ARRAY_LENGTH(input_buffer_prebuffer) / input_params.channels;
752
0
  seq_idx = seq(input_buffer_prebuffer, input_channels, seq_idx,
753
0
                prebuffer_frames);
754
0
755
0
  long got = cubeb_resampler_fill(resampler, input_buffer_prebuffer, &prebuffer_frames,
756
0
                                  output_buffer, BUF_BASE_SIZE);
757
0
758
0
  output_seq_idx += BUF_BASE_SIZE;
759
0
760
0
  // prebuffer_frames will hold the frames used by the resampler.
761
0
  ASSERT_EQ(prebuffer_frames, BUF_BASE_SIZE);
762
0
  ASSERT_EQ(got, BUF_BASE_SIZE);
763
0
764
0
  for (uint32_t i = 0; i < 300; i++) {
765
0
    long int frames = BUF_BASE_SIZE;
766
0
    // Simulate that sometimes, we don't have the input callback on time
767
0
    if (i != 0 && (i % 100) == 0) {
768
0
      long zero = 0;
769
0
      got = cubeb_resampler_fill(resampler, input_buffer_normal /* unused here */,
770
0
                                 &zero, output_buffer, BUF_BASE_SIZE);
771
0
      is_seq(output_buffer, 2, BUF_BASE_SIZE, output_seq_idx);
772
0
      output_seq_idx += BUF_BASE_SIZE;
773
0
    } else if (i != 0 && (i % 100) == 1) {
774
0
      // if this is the case, the on the next iteration, we'll have twice the
775
0
      // amount of input frames
776
0
      seq_idx = seq(input_buffer_glitch, input_channels, seq_idx, BUF_BASE_SIZE * 2);
777
0
      frames = 2 * BUF_BASE_SIZE;
778
0
      got = cubeb_resampler_fill(resampler, input_buffer_glitch, &frames, output_buffer, BUF_BASE_SIZE);
779
0
      is_seq(output_buffer, 2, BUF_BASE_SIZE, output_seq_idx);
780
0
      output_seq_idx += BUF_BASE_SIZE;
781
0
    } else {
782
0
       // normal case
783
0
      seq_idx = seq(input_buffer_normal, input_channels, seq_idx, BUF_BASE_SIZE);
784
0
      long normal_input_frame_count = 256;
785
0
      got = cubeb_resampler_fill(resampler, input_buffer_normal, &normal_input_frame_count, output_buffer, BUF_BASE_SIZE);
786
0
      is_seq(output_buffer, 2, BUF_BASE_SIZE, output_seq_idx);
787
0
      output_seq_idx += BUF_BASE_SIZE;
788
0
    }
789
0
    ASSERT_EQ(got, BUF_BASE_SIZE);
790
0
  }
791
0
792
0
  cubeb_resampler_destroy(resampler);
793
0
}
794
795
// Artificially simulate output thread underruns,
796
// by building up artificial delay in the input.
797
// Check that the frame drop logic kicks in.
798
TEST(cubeb, resampler_drift_drop_data)
799
0
{
800
0
  for (uint32_t input_channels = 1; input_channels < 3; input_channels++) {
801
0
    cubeb_stream_params input_params;
802
0
    cubeb_stream_params output_params;
803
0
804
0
    const int output_channels = 2;
805
0
    const int sample_rate = 44100;
806
0
807
0
    input_params.channels = input_channels;
808
0
    input_params.rate = sample_rate;
809
0
    input_params.format = CUBEB_SAMPLE_FLOAT32NE;
810
0
811
0
    output_params.channels = output_channels;
812
0
    output_params.rate = sample_rate;
813
0
    output_params.format = CUBEB_SAMPLE_FLOAT32NE;
814
0
815
0
    int target_rate = input_params.rate;
816
0
817
0
    closure c;
818
0
    c.input_channel_count = input_channels;
819
0
820
0
    cubeb_resampler * resampler =
821
0
      cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params,
822
0
        target_rate, cb_passthrough_resampler_duplex, &c,
823
0
        CUBEB_RESAMPLER_QUALITY_VOIP);
824
0
825
0
    const long BUF_BASE_SIZE = 256;
826
0
827
0
    // The factor by which the deadline is missed. This is intentionally
828
0
    // kind of large to trigger the frame drop quickly. In real life, multiple
829
0
    // smaller under-runs would accumulate.
830
0
    const long UNDERRUN_FACTOR = 10;
831
0
    // Number buffer used for pre-buffering, that some backends do.
832
0
    const long PREBUFFER_FACTOR = 2;
833
0
834
0
    std::vector<float> input_buffer_prebuffer(input_channels * BUF_BASE_SIZE * PREBUFFER_FACTOR);
835
0
    std::vector<float> input_buffer_glitch(input_channels * BUF_BASE_SIZE * UNDERRUN_FACTOR);
836
0
    std::vector<float> input_buffer_normal(input_channels * BUF_BASE_SIZE);
837
0
    std::vector<float> output_buffer(output_channels * BUF_BASE_SIZE);
838
0
839
0
    long seq_idx = 0;
840
0
    long output_seq_idx = 0;
841
0
842
0
    long prebuffer_frames = input_buffer_prebuffer.size() / input_params.channels;
843
0
    seq_idx = seq(input_buffer_prebuffer.data(), input_channels, seq_idx,
844
0
      prebuffer_frames);
845
0
846
0
    long got = cubeb_resampler_fill(resampler, input_buffer_prebuffer.data(), &prebuffer_frames,
847
0
      output_buffer.data(), BUF_BASE_SIZE);
848
0
849
0
    output_seq_idx += BUF_BASE_SIZE;
850
0
851
0
    // prebuffer_frames will hold the frames used by the resampler.
852
0
    ASSERT_EQ(prebuffer_frames, BUF_BASE_SIZE);
853
0
    ASSERT_EQ(got, BUF_BASE_SIZE);
854
0
855
0
    for (uint32_t i = 0; i < 300; i++) {
856
0
      long int frames = BUF_BASE_SIZE;
857
0
      if (i != 0 && (i % 100) == 1) {
858
0
        // Once in a while, the output thread misses its deadline.
859
0
        // The input thread still produces data, so it ends up accumulating. Simulate this by providing a
860
0
        // much bigger input buffer. Check that the sequence is now unaligned, meaning we've dropped data
861
0
        // to keep everything in sync.
862
0
        seq_idx = seq(input_buffer_glitch.data(), input_channels, seq_idx, BUF_BASE_SIZE * UNDERRUN_FACTOR);
863
0
        frames = BUF_BASE_SIZE * UNDERRUN_FACTOR;
864
0
        got = cubeb_resampler_fill(resampler, input_buffer_glitch.data(), &frames, output_buffer.data(), BUF_BASE_SIZE);
865
0
        is_seq(output_buffer.data(), 2, BUF_BASE_SIZE, output_seq_idx);
866
0
        output_seq_idx += BUF_BASE_SIZE;
867
0
      }
868
0
      else if (i != 0 && (i % 100) == 2) {
869
0
        // On the next iteration, the sequence should be broken
870
0
        seq_idx = seq(input_buffer_normal.data(), input_channels, seq_idx, BUF_BASE_SIZE);
871
0
        long normal_input_frame_count = 256;
872
0
        got = cubeb_resampler_fill(resampler, input_buffer_normal.data(), &normal_input_frame_count, output_buffer.data(), BUF_BASE_SIZE);
873
0
        is_not_seq(output_buffer.data(), output_channels, BUF_BASE_SIZE, output_seq_idx);
874
0
        // Reclock so that we can use is_seq again.
875
0
        output_seq_idx = output_buffer[BUF_BASE_SIZE * output_channels - 1] + 1;
876
0
      }
877
0
      else {
878
0
        // normal case
879
0
        seq_idx = seq(input_buffer_normal.data(), input_channels, seq_idx, BUF_BASE_SIZE);
880
0
        long normal_input_frame_count = 256;
881
0
        got = cubeb_resampler_fill(resampler, input_buffer_normal.data(), &normal_input_frame_count, output_buffer.data(), BUF_BASE_SIZE);
882
0
        is_seq(output_buffer.data(), output_channels, BUF_BASE_SIZE, output_seq_idx);
883
0
        output_seq_idx += BUF_BASE_SIZE;
884
0
      }
885
0
      ASSERT_EQ(got, BUF_BASE_SIZE);
886
0
    }
887
0
888
0
    cubeb_resampler_destroy(resampler);
889
0
  }
890
0
}
891