summaryrefslogtreecommitdiffstats
path: root/chromium/third_party/webrtc/modules/audio_processing/aec/system_delay_unittest.cc
blob: a13d47622c913a761a3cf677709a1a3ff207f288 (plain)
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
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
/*
 *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "testing/gtest/include/gtest/gtest.h"
extern "C" {
#include "webrtc/modules/audio_processing/aec/aec_core.h"
}
#include "webrtc/modules/audio_processing/aec/echo_cancellation_internal.h"
#include "webrtc/modules/audio_processing/aec/include/echo_cancellation.h"
#include "webrtc/test/testsupport/gtest_disable.h"
#include "webrtc/typedefs.h"

namespace {

class SystemDelayTest : public ::testing::Test {
 protected:
  SystemDelayTest();
  virtual void SetUp();
  virtual void TearDown();

  // Initialization of AEC handle with respect to |sample_rate_hz|. Since the
  // device sample rate is unimportant we set that value to 48000 Hz.
  void Init(int sample_rate_hz);

  // Makes one render call and one capture call in that specific order.
  void RenderAndCapture(int device_buffer_ms);

  // Fills up the far-end buffer with respect to the default device buffer size.
  int BufferFillUp();

  // Runs and verifies the behavior in a stable startup procedure.
  void RunStableStartup();

  // Maps buffer size in ms into samples, taking the unprocessed frame into
  // account.
  int MapBufferSizeToSamples(int size_in_ms);

  void* handle_;
  aecpc_t* self_;
  int samples_per_frame_;
  // Dummy input/output speech data.
  static const int kSamplesPerChunk = 160;
  int16_t far_[kSamplesPerChunk];
  float near_[kSamplesPerChunk];
  float out_[kSamplesPerChunk];
};

SystemDelayTest::SystemDelayTest()
    : handle_(NULL), self_(NULL), samples_per_frame_(0) {
  // Dummy input data are set with more or less arbitrary non-zero values.
  memset(far_, 1, sizeof(far_));
  for (int i = 0; i < kSamplesPerChunk; i++)
    near_[i] = 514.0;
  memset(out_, 0, sizeof(out_));
}

void SystemDelayTest::SetUp() {
  ASSERT_EQ(0, WebRtcAec_Create(&handle_));
  self_ = reinterpret_cast<aecpc_t*>(handle_);
}

void SystemDelayTest::TearDown() {
  // Free AEC
  ASSERT_EQ(0, WebRtcAec_Free(handle_));
  handle_ = NULL;
}

// In SWB mode nothing is added to the buffer handling with respect to
// functionality compared to WB. We therefore only verify behavior in NB and WB.
static const int kSampleRateHz[] = {8000, 16000};
static const size_t kNumSampleRates =
    sizeof(kSampleRateHz) / sizeof(*kSampleRateHz);

// Default audio device buffer size used.
static const int kDeviceBufMs = 100;

// Requirement for a stable device convergence time in ms. Should converge in
// less than |kStableConvergenceMs|.
static const int kStableConvergenceMs = 100;

// Maximum convergence time in ms. This means that we should leave the startup
// phase after |kMaxConvergenceMs| independent of device buffer stability
// conditions.
static const int kMaxConvergenceMs = 500;

void SystemDelayTest::Init(int sample_rate_hz) {
  // Initialize AEC
  EXPECT_EQ(0, WebRtcAec_Init(handle_, sample_rate_hz, 48000));

  // One frame equals 10 ms of data.
  samples_per_frame_ = sample_rate_hz / 100;
}

void SystemDelayTest::RenderAndCapture(int device_buffer_ms) {
  EXPECT_EQ(0, WebRtcAec_BufferFarend(handle_, far_, samples_per_frame_));
  EXPECT_EQ(0,
            WebRtcAec_Process(handle_,
                              near_,
                              NULL,
                              out_,
                              NULL,
                              samples_per_frame_,
                              device_buffer_ms,
                              0));
}

int SystemDelayTest::BufferFillUp() {
  // To make sure we have a full buffer when we verify stability we first fill
  // up the far-end buffer with the same amount as we will report in through
  // Process().
  int buffer_size = 0;
  for (int i = 0; i < kDeviceBufMs / 10; i++) {
    EXPECT_EQ(0, WebRtcAec_BufferFarend(handle_, far_, samples_per_frame_));
    buffer_size += samples_per_frame_;
    EXPECT_EQ(buffer_size, WebRtcAec_system_delay(self_->aec));
  }
  return buffer_size;
}

void SystemDelayTest::RunStableStartup() {
  // To make sure we have a full buffer when we verify stability we first fill
  // up the far-end buffer with the same amount as we will report in through
  // Process().
  int buffer_size = BufferFillUp();
  // A stable device should be accepted and put in a regular process mode within
  // |kStableConvergenceMs|.
  int process_time_ms = 0;
  for (; process_time_ms < kStableConvergenceMs; process_time_ms += 10) {
    RenderAndCapture(kDeviceBufMs);
    buffer_size += samples_per_frame_;
    if (self_->startup_phase == 0) {
      // We have left the startup phase.
      break;
    }
  }
  // Verify convergence time.
  EXPECT_GT(kStableConvergenceMs, process_time_ms);
  // Verify that the buffer has been flushed.
  EXPECT_GE(buffer_size, WebRtcAec_system_delay(self_->aec));
}

int SystemDelayTest::MapBufferSizeToSamples(int size_in_ms) {
  // The extra 10 ms corresponds to the unprocessed frame.
  return (size_in_ms + 10) * samples_per_frame_ / 10;
}

// The tests should meet basic requirements and not be adjusted to what is
// actually implemented. If we don't get good code coverage this way we either
// lack in tests or have unnecessary code.
// General requirements:
// 1) If we add far-end data the system delay should be increased with the same
//    amount we add.
// 2) If the far-end buffer is full we should flush the oldest data to make room
//    for the new. In this case the system delay is unaffected.
// 3) There should exist a startup phase in which the buffer size is to be
//    determined. In this phase no cancellation should be performed.
// 4) Under stable conditions (small variations in device buffer sizes) the AEC
//    should determine an appropriate local buffer size within
//    |kStableConvergenceMs| ms.
// 5) Under unstable conditions the AEC should make a decision within
//    |kMaxConvergenceMs| ms.
// 6) If the local buffer runs out of data we should stuff the buffer with older
//    frames.
// 7) The system delay should within |kMaxConvergenceMs| ms heal from
//    disturbances like drift, data glitches, toggling events and outliers.
// 8) The system delay should never become negative.

TEST_F(SystemDelayTest, CorrectIncreaseWhenBufferFarend) {
  // When we add data to the AEC buffer the internal system delay should be
  // incremented with the same amount as the size of data.
  for (size_t i = 0; i < kNumSampleRates; i++) {
    Init(kSampleRateHz[i]);

    // Loop through a couple of calls to make sure the system delay increments
    // correctly.
    for (int j = 1; j <= 5; j++) {
      EXPECT_EQ(0, WebRtcAec_BufferFarend(handle_, far_, samples_per_frame_));
      EXPECT_EQ(j * samples_per_frame_, WebRtcAec_system_delay(self_->aec));
    }
  }
}

// TODO(bjornv): Add a test to verify behavior if the far-end buffer is full
// when adding new data.

TEST_F(SystemDelayTest, CorrectDelayAfterStableStartup) {
  // We run the system in a stable startup. After that we verify that the system
  // delay meets the requirements.
  for (size_t i = 0; i < kNumSampleRates; i++) {
    Init(kSampleRateHz[i]);
    RunStableStartup();

    // Verify system delay with respect to requirements, i.e., the
    // |system_delay| is in the interval [75%, 100%] of what's reported on the
    // average.
    int average_reported_delay = kDeviceBufMs * samples_per_frame_ / 10;
    EXPECT_GE(average_reported_delay, WebRtcAec_system_delay(self_->aec));
    EXPECT_LE(average_reported_delay * 3 / 4,
              WebRtcAec_system_delay(self_->aec));
  }
}

TEST_F(SystemDelayTest, CorrectDelayAfterUnstableStartup) {
  // In an unstable system we would start processing after |kMaxConvergenceMs|.
  // On the last frame the AEC buffer is adjusted to 60% of the last reported
  // device buffer size.
  // We construct an unstable system by altering the device buffer size between
  // two values |kDeviceBufMs| +- 25 ms.
  for (size_t i = 0; i < kNumSampleRates; i++) {
    Init(kSampleRateHz[i]);

    // To make sure we have a full buffer when we verify stability we first fill
    // up the far-end buffer with the same amount as we will report in on the
    // average through Process().
    int buffer_size = BufferFillUp();

    int buffer_offset_ms = 25;
    int reported_delay_ms = 0;
    int process_time_ms = 0;
    for (; process_time_ms <= kMaxConvergenceMs; process_time_ms += 10) {
      reported_delay_ms = kDeviceBufMs + buffer_offset_ms;
      RenderAndCapture(reported_delay_ms);
      buffer_size += samples_per_frame_;
      buffer_offset_ms = -buffer_offset_ms;
      if (self_->startup_phase == 0) {
        // We have left the startup phase.
        break;
      }
    }
    // Verify convergence time.
    EXPECT_GE(kMaxConvergenceMs, process_time_ms);
    // Verify that the buffer has been flushed.
    EXPECT_GE(buffer_size, WebRtcAec_system_delay(self_->aec));

    // Verify system delay with respect to requirements, i.e., the
    // |system_delay| is in the interval [60%, 100%] of what's last reported.
    EXPECT_GE(reported_delay_ms * samples_per_frame_ / 10,
              WebRtcAec_system_delay(self_->aec));
    EXPECT_LE(reported_delay_ms * samples_per_frame_ / 10 * 3 / 5,
              WebRtcAec_system_delay(self_->aec));
  }
}

TEST_F(SystemDelayTest, CorrectDelayAfterStableBufferBuildUp) {
  // In this test we start by establishing the device buffer size during stable
  // conditions, but with an empty internal far-end buffer. Once that is done we
  // verify that the system delay is increased correctly until we have reach an
  // internal buffer size of 75% of what's been reported.

  // This test assumes the reported delays are used.
  WebRtcAec_enable_reported_delay(WebRtcAec_aec_core(handle_), 1);
  for (size_t i = 0; i < kNumSampleRates; i++) {
    Init(kSampleRateHz[i]);

    // We assume that running |kStableConvergenceMs| calls will put the
    // algorithm in a state where the device buffer size has been determined. We
    // can make that assumption since we have a separate stability test.
    int process_time_ms = 0;
    for (; process_time_ms < kStableConvergenceMs; process_time_ms += 10) {
      EXPECT_EQ(0,
                WebRtcAec_Process(handle_,
                                  near_,
                                  NULL,
                                  out_,
                                  NULL,
                                  samples_per_frame_,
                                  kDeviceBufMs,
                                  0));
    }
    // Verify that a buffer size has been established.
    EXPECT_EQ(0, self_->checkBuffSize);

    // We now have established the required buffer size. Let us verify that we
    // fill up before leaving the startup phase for normal processing.
    int buffer_size = 0;
    int target_buffer_size = kDeviceBufMs * samples_per_frame_ / 10 * 3 / 4;
    process_time_ms = 0;
    for (; process_time_ms <= kMaxConvergenceMs; process_time_ms += 10) {
      RenderAndCapture(kDeviceBufMs);
      buffer_size += samples_per_frame_;
      if (self_->startup_phase == 0) {
        // We have left the startup phase.
        break;
      }
    }
    // Verify convergence time.
    EXPECT_GT(kMaxConvergenceMs, process_time_ms);
    // Verify that the buffer has reached the desired size.
    EXPECT_LE(target_buffer_size, WebRtcAec_system_delay(self_->aec));

    // Verify normal behavior (system delay is kept constant) after startup by
    // running a couple of calls to BufferFarend() and Process().
    for (int j = 0; j < 6; j++) {
      int system_delay_before_calls = WebRtcAec_system_delay(self_->aec);
      RenderAndCapture(kDeviceBufMs);
      EXPECT_EQ(system_delay_before_calls, WebRtcAec_system_delay(self_->aec));
    }
  }
}

TEST_F(SystemDelayTest, CorrectDelayWhenBufferUnderrun) {
  // Here we test a buffer under run scenario. If we keep on calling
  // WebRtcAec_Process() we will finally run out of data, but should
  // automatically stuff the buffer. We verify this behavior by checking if the
  // system delay goes negative.
  for (size_t i = 0; i < kNumSampleRates; i++) {
    Init(kSampleRateHz[i]);
    RunStableStartup();

    // The AEC has now left the Startup phase. We now have at most
    // |kStableConvergenceMs| in the buffer. Keep on calling Process() until
    // we run out of data and verify that the system delay is non-negative.
    for (int j = 0; j <= kStableConvergenceMs; j += 10) {
      EXPECT_EQ(0,
                WebRtcAec_Process(handle_,
                                  near_,
                                  NULL,
                                  out_,
                                  NULL,
                                  samples_per_frame_,
                                  kDeviceBufMs,
                                  0));
      EXPECT_LE(0, WebRtcAec_system_delay(self_->aec));
    }
  }
}

TEST_F(SystemDelayTest, CorrectDelayDuringDrift) {
  // This drift test should verify that the system delay is never exceeding the
  // device buffer. The drift is simulated by decreasing the reported device
  // buffer size by 1 ms every 100 ms. If the device buffer size goes below 30
  // ms we jump (add) 10 ms to give a repeated pattern.

  // This test assumes the reported delays are used.
  WebRtcAec_enable_reported_delay(WebRtcAec_aec_core(handle_), 1);
  for (size_t i = 0; i < kNumSampleRates; i++) {
    Init(kSampleRateHz[i]);
    RunStableStartup();

    // We have now left the startup phase and proceed with normal processing.
    int jump = 0;
    for (int j = 0; j < 1000; j++) {
      // Drift = -1 ms per 100 ms of data.
      int device_buf_ms = kDeviceBufMs - (j / 10) + jump;
      int device_buf = MapBufferSizeToSamples(device_buf_ms);

      if (device_buf_ms < 30) {
        // Add 10 ms data, taking affect next frame.
        jump += 10;
      }
      RenderAndCapture(device_buf_ms);

      // Verify that the system delay does not exceed the device buffer.
      EXPECT_GE(device_buf, WebRtcAec_system_delay(self_->aec));

      // Verify that the system delay is non-negative.
      EXPECT_LE(0, WebRtcAec_system_delay(self_->aec));
    }
  }
}

TEST_F(SystemDelayTest, ShouldRecoverAfterGlitch) {
  // This glitch test should verify that the system delay recovers if there is
  // a glitch in data. The data glitch is constructed as 200 ms of buffering
  // after which the stable procedure continues. The glitch is never reported by
  // the device.
  // The system is said to be in a non-causal state if the difference between
  // the device buffer and system delay is less than a block (64 samples).

  // This test assumes the reported delays are used.
  WebRtcAec_enable_reported_delay(WebRtcAec_aec_core(handle_), 1);
  for (size_t i = 0; i < kNumSampleRates; i++) {
    Init(kSampleRateHz[i]);
    RunStableStartup();
    int device_buf = MapBufferSizeToSamples(kDeviceBufMs);
    // Glitch state.
    for (int j = 0; j < 20; j++) {
      EXPECT_EQ(0, WebRtcAec_BufferFarend(handle_, far_, samples_per_frame_));
      // No need to verify system delay, since that is done in a separate test.
    }
    // Verify that we are in a non-causal state, i.e.,
    // |system_delay| > |device_buf|.
    EXPECT_LT(device_buf, WebRtcAec_system_delay(self_->aec));

    // Recover state. Should recover at least 4 ms of data per 10 ms, hence a
    // glitch of 200 ms will take at most 200 * 10 / 4 = 500 ms to recover from.
    bool non_causal = true;  // We are currently in a non-causal state.
    for (int j = 0; j < 50; j++) {
      int system_delay_before = WebRtcAec_system_delay(self_->aec);
      RenderAndCapture(kDeviceBufMs);
      int system_delay_after = WebRtcAec_system_delay(self_->aec);

      // We have recovered if |device_buf| - |system_delay_after| >= 64 (one
      // block). During recovery |system_delay_after| < |system_delay_before|,
      // otherwise they are equal.
      if (non_causal) {
        EXPECT_LT(system_delay_after, system_delay_before);
        if (device_buf - system_delay_after >= 64) {
          non_causal = false;
        }
      } else {
        EXPECT_EQ(system_delay_before, system_delay_after);
      }
      // Verify that the system delay is non-negative.
      EXPECT_LE(0, WebRtcAec_system_delay(self_->aec));
    }
    // Check that we have recovered.
    EXPECT_FALSE(non_causal);
  }
}

TEST_F(SystemDelayTest, UnaffectedWhenSpuriousDeviceBufferValues) {
  // This spurious device buffer data test aims at verifying that the system
  // delay is unaffected by large outliers.
  // The system is said to be in a non-causal state if the difference between
  // the device buffer and system delay is less than a block (64 samples).
  for (size_t i = 0; i < kNumSampleRates; i++) {
    Init(kSampleRateHz[i]);
    RunStableStartup();
    int device_buf = MapBufferSizeToSamples(kDeviceBufMs);

    // Normal state. We are currently not in a non-causal state.
    bool non_causal = false;

    // Run 1 s and replace device buffer size with 500 ms every 100 ms.
    for (int j = 0; j < 100; j++) {
      int system_delay_before_calls = WebRtcAec_system_delay(self_->aec);
      int device_buf_ms = kDeviceBufMs;
      if (j % 10 == 0) {
        device_buf_ms = 500;
      }
      RenderAndCapture(device_buf_ms);

      // Check for non-causality.
      if (device_buf - WebRtcAec_system_delay(self_->aec) < 64) {
        non_causal = true;
      }
      EXPECT_FALSE(non_causal);
      EXPECT_EQ(system_delay_before_calls, WebRtcAec_system_delay(self_->aec));

      // Verify that the system delay is non-negative.
      EXPECT_LE(0, WebRtcAec_system_delay(self_->aec));
    }
  }
}

TEST_F(SystemDelayTest, CorrectImpactWhenTogglingDeviceBufferValues) {
  // This test aims at verifying that the system delay is "unaffected" by
  // toggling values reported by the device.
  // The test is constructed such that every other device buffer value is zero
  // and then 2 * |kDeviceBufMs|, hence the size is constant on the average. The
  // zero values will force us into a non-causal state and thereby lowering the
  // system delay until we basically runs out of data. Once that happens the
  // buffer will be stuffed.
  // TODO(bjornv): This test will have a better impact if we verified that the
  // delay estimate goes up when the system delay goes done to meet the average
  // device buffer size.
  for (size_t i = 0; i < kNumSampleRates; i++) {
    Init(kSampleRateHz[i]);
    RunStableStartup();
    int device_buf = MapBufferSizeToSamples(kDeviceBufMs);

    // Normal state. We are currently not in a non-causal state.
    bool non_causal = false;

    // Loop through 100 frames (both render and capture), which equals 1 s of
    // data. Every odd frame we set the device buffer size to 2 * |kDeviceBufMs|
    // and even frames we set the device buffer size to zero.
    for (int j = 0; j < 100; j++) {
      int system_delay_before_calls = WebRtcAec_system_delay(self_->aec);
      int device_buf_ms = 2 * (j % 2) * kDeviceBufMs;
      RenderAndCapture(device_buf_ms);

      // Check for non-causality, compared with the average device buffer size.
      non_causal |= (device_buf - WebRtcAec_system_delay(self_->aec) < 64);
      EXPECT_GE(system_delay_before_calls, WebRtcAec_system_delay(self_->aec));

      // Verify that the system delay is non-negative.
      EXPECT_LE(0, WebRtcAec_system_delay(self_->aec));
    }
    // Verify we are not in a non-causal state.
    EXPECT_FALSE(non_causal);
  }
}

}  // namespace