1 /*
2 * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11 #include <stdio.h>
12 #include <string.h>
13 #ifdef WEBRTC_ANDROID
14 #include <sys/stat.h>
15 #endif
16
17 #include "gtest/gtest.h"
18
19 #include "audio_processing.h"
20 #include "cpu_features_wrapper.h"
21 #include "module_common_types.h"
22 #include "scoped_ptr.h"
23 #include "tick_util.h"
24 #ifdef WEBRTC_ANDROID
25 #include "external/webrtc/src/modules/audio_processing/debug.pb.h"
26 #else
27 #include "webrtc/audio_processing/debug.pb.h"
28 #endif
29
30 using webrtc::AudioFrame;
31 using webrtc::AudioProcessing;
32 using webrtc::EchoCancellation;
33 using webrtc::GainControl;
34 using webrtc::NoiseSuppression;
35 using webrtc::scoped_array;
36 using webrtc::TickInterval;
37 using webrtc::TickTime;
38
39 using webrtc::audioproc::Event;
40 using webrtc::audioproc::Init;
41 using webrtc::audioproc::ReverseStream;
42 using webrtc::audioproc::Stream;
43
44 namespace {
45 // Returns true on success, false on error or end-of-file.
ReadMessageFromFile(FILE * file,::google::protobuf::MessageLite * msg)46 bool ReadMessageFromFile(FILE* file,
47 ::google::protobuf::MessageLite* msg) {
48 // The "wire format" for the size is little-endian.
49 // Assume process_test is running on a little-endian machine.
50 int32_t size = 0;
51 if (fread(&size, sizeof(int32_t), 1, file) != 1) {
52 return false;
53 }
54 if (size <= 0) {
55 return false;
56 }
57 const size_t usize = static_cast<size_t>(size);
58
59 scoped_array<char> array(new char[usize]);
60 if (fread(array.get(), sizeof(char), usize, file) != usize) {
61 return false;
62 }
63
64 msg->Clear();
65 return msg->ParseFromArray(array.get(), usize);
66 }
67
PrintStat(const AudioProcessing::Statistic & stat)68 void PrintStat(const AudioProcessing::Statistic& stat) {
69 printf("%d, %d, %d\n", stat.average,
70 stat.maximum,
71 stat.minimum);
72 }
73
usage()74 void usage() {
75 printf(
76 "Usage: process_test [options] [-pb PROTOBUF_FILE]\n"
77 " [-ir REVERSE_FILE] [-i PRIMARY_FILE] [-o OUT_FILE]\n");
78 printf(
79 "process_test is a test application for AudioProcessing.\n\n"
80 "When a protobuf debug file is available, specify it with -pb.\n"
81 "Alternately, when -ir or -i is used, the specified files will be\n"
82 "processed directly in a simulation mode. Otherwise the full set of\n"
83 "legacy test files is expected to be present in the working directory.\n");
84 printf("\n");
85 printf("Options\n");
86 printf("General configuration (only used for the simulation mode):\n");
87 printf(" -fs SAMPLE_RATE_HZ\n");
88 printf(" -ch CHANNELS_IN CHANNELS_OUT\n");
89 printf(" -rch REVERSE_CHANNELS\n");
90 printf("\n");
91 printf("Component configuration:\n");
92 printf(
93 "All components are disabled by default. Each block below begins with a\n"
94 "flag to enable the component with default settings. The subsequent flags\n"
95 "in the block are used to provide configuration settings.\n");
96 printf("\n -aec Echo cancellation\n");
97 printf(" --drift_compensation\n");
98 printf(" --no_drift_compensation\n");
99 printf(" --no_echo_metrics\n");
100 printf(" --no_delay_logging\n");
101 printf("\n -aecm Echo control mobile\n");
102 printf(" --aecm_echo_path_in_file FILE\n");
103 printf(" --aecm_echo_path_out_file FILE\n");
104 printf("\n -agc Gain control\n");
105 printf(" --analog\n");
106 printf(" --adaptive_digital\n");
107 printf(" --fixed_digital\n");
108 printf(" --target_level LEVEL\n");
109 printf(" --compression_gain GAIN\n");
110 printf(" --limiter\n");
111 printf(" --no_limiter\n");
112 printf("\n -hpf High pass filter\n");
113 printf("\n -ns Noise suppression\n");
114 printf(" --ns_low\n");
115 printf(" --ns_moderate\n");
116 printf(" --ns_high\n");
117 printf(" --ns_very_high\n");
118 printf("\n -vad Voice activity detection\n");
119 printf(" --vad_out_file FILE\n");
120 printf("\n Level metrics (enabled by default)\n");
121 printf(" --no_level_metrics\n");
122 printf("\n");
123 printf("Modifiers:\n");
124 printf(" --noasm Disable SSE optimization.\n");
125 printf(" --delay DELAY Add DELAY ms to input value.\n");
126 printf(" --perf Measure performance.\n");
127 printf(" --quiet Suppress text output.\n");
128 printf(" --no_progress Suppress progress.\n");
129 printf(" --debug_file FILE Dump a debug recording.\n");
130 }
131
132 // void function for gtest.
void_main(int argc,char * argv[])133 void void_main(int argc, char* argv[]) {
134 if (argc > 1 && strcmp(argv[1], "--help") == 0) {
135 usage();
136 return;
137 }
138
139 if (argc < 2) {
140 printf("Did you mean to run without arguments?\n");
141 printf("Try `process_test --help' for more information.\n\n");
142 }
143
144 AudioProcessing* apm = AudioProcessing::Create(0);
145 ASSERT_TRUE(apm != NULL);
146
147 const char* pb_filename = NULL;
148 const char* far_filename = NULL;
149 const char* near_filename = NULL;
150 const char* out_filename = NULL;
151 const char* vad_out_filename = NULL;
152 const char* aecm_echo_path_in_filename = NULL;
153 const char* aecm_echo_path_out_filename = NULL;
154
155 int32_t sample_rate_hz = 16000;
156 int32_t device_sample_rate_hz = 16000;
157
158 int num_capture_input_channels = 1;
159 int num_capture_output_channels = 1;
160 int num_render_channels = 1;
161
162 int samples_per_channel = sample_rate_hz / 100;
163
164 bool simulating = false;
165 bool perf_testing = false;
166 bool verbose = true;
167 bool progress = true;
168 int extra_delay_ms = 0;
169 //bool interleaved = true;
170
171 ASSERT_EQ(apm->kNoError, apm->level_estimator()->Enable(true));
172 for (int i = 1; i < argc; i++) {
173 if (strcmp(argv[i], "-pb") == 0) {
174 i++;
175 ASSERT_LT(i, argc) << "Specify protobuf filename after -pb";
176 pb_filename = argv[i];
177
178 } else if (strcmp(argv[i], "-ir") == 0) {
179 i++;
180 ASSERT_LT(i, argc) << "Specify filename after -ir";
181 far_filename = argv[i];
182 simulating = true;
183
184 } else if (strcmp(argv[i], "-i") == 0) {
185 i++;
186 ASSERT_LT(i, argc) << "Specify filename after -i";
187 near_filename = argv[i];
188 simulating = true;
189
190 } else if (strcmp(argv[i], "-o") == 0) {
191 i++;
192 ASSERT_LT(i, argc) << "Specify filename after -o";
193 out_filename = argv[i];
194
195 } else if (strcmp(argv[i], "-fs") == 0) {
196 i++;
197 ASSERT_LT(i, argc) << "Specify sample rate after -fs";
198 ASSERT_EQ(1, sscanf(argv[i], "%d", &sample_rate_hz));
199 samples_per_channel = sample_rate_hz / 100;
200
201 ASSERT_EQ(apm->kNoError,
202 apm->set_sample_rate_hz(sample_rate_hz));
203
204 } else if (strcmp(argv[i], "-ch") == 0) {
205 i++;
206 ASSERT_LT(i + 1, argc) << "Specify number of channels after -ch";
207 ASSERT_EQ(1, sscanf(argv[i], "%d", &num_capture_input_channels));
208 i++;
209 ASSERT_EQ(1, sscanf(argv[i], "%d", &num_capture_output_channels));
210
211 ASSERT_EQ(apm->kNoError,
212 apm->set_num_channels(num_capture_input_channels,
213 num_capture_output_channels));
214
215 } else if (strcmp(argv[i], "-rch") == 0) {
216 i++;
217 ASSERT_LT(i, argc) << "Specify number of channels after -rch";
218 ASSERT_EQ(1, sscanf(argv[i], "%d", &num_render_channels));
219
220 ASSERT_EQ(apm->kNoError,
221 apm->set_num_reverse_channels(num_render_channels));
222
223 } else if (strcmp(argv[i], "-aec") == 0) {
224 ASSERT_EQ(apm->kNoError, apm->echo_cancellation()->Enable(true));
225 ASSERT_EQ(apm->kNoError,
226 apm->echo_cancellation()->enable_metrics(true));
227 ASSERT_EQ(apm->kNoError,
228 apm->echo_cancellation()->enable_delay_logging(true));
229
230 } else if (strcmp(argv[i], "--drift_compensation") == 0) {
231 ASSERT_EQ(apm->kNoError, apm->echo_cancellation()->Enable(true));
232 // TODO(ajm): this is enabled in the VQE test app by default. Investigate
233 // why it can give better performance despite passing zeros.
234 ASSERT_EQ(apm->kNoError,
235 apm->echo_cancellation()->enable_drift_compensation(true));
236 } else if (strcmp(argv[i], "--no_drift_compensation") == 0) {
237 ASSERT_EQ(apm->kNoError, apm->echo_cancellation()->Enable(true));
238 ASSERT_EQ(apm->kNoError,
239 apm->echo_cancellation()->enable_drift_compensation(false));
240
241 } else if (strcmp(argv[i], "--no_echo_metrics") == 0) {
242 ASSERT_EQ(apm->kNoError, apm->echo_cancellation()->Enable(true));
243 ASSERT_EQ(apm->kNoError,
244 apm->echo_cancellation()->enable_metrics(false));
245
246 } else if (strcmp(argv[i], "--no_delay_logging") == 0) {
247 ASSERT_EQ(apm->kNoError, apm->echo_cancellation()->Enable(true));
248 ASSERT_EQ(apm->kNoError,
249 apm->echo_cancellation()->enable_delay_logging(false));
250
251 } else if (strcmp(argv[i], "--no_level_metrics") == 0) {
252 ASSERT_EQ(apm->kNoError, apm->level_estimator()->Enable(false));
253
254 } else if (strcmp(argv[i], "-aecm") == 0) {
255 ASSERT_EQ(apm->kNoError, apm->echo_control_mobile()->Enable(true));
256
257 } else if (strcmp(argv[i], "--aecm_echo_path_in_file") == 0) {
258 i++;
259 ASSERT_LT(i, argc) << "Specify filename after --aecm_echo_path_in_file";
260 aecm_echo_path_in_filename = argv[i];
261
262 } else if (strcmp(argv[i], "--aecm_echo_path_out_file") == 0) {
263 i++;
264 ASSERT_LT(i, argc) << "Specify filename after --aecm_echo_path_out_file";
265 aecm_echo_path_out_filename = argv[i];
266
267 } else if (strcmp(argv[i], "-agc") == 0) {
268 ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
269
270 } else if (strcmp(argv[i], "--analog") == 0) {
271 ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
272 ASSERT_EQ(apm->kNoError,
273 apm->gain_control()->set_mode(GainControl::kAdaptiveAnalog));
274
275 } else if (strcmp(argv[i], "--adaptive_digital") == 0) {
276 ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
277 ASSERT_EQ(apm->kNoError,
278 apm->gain_control()->set_mode(GainControl::kAdaptiveDigital));
279
280 } else if (strcmp(argv[i], "--fixed_digital") == 0) {
281 ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
282 ASSERT_EQ(apm->kNoError,
283 apm->gain_control()->set_mode(GainControl::kFixedDigital));
284
285 } else if (strcmp(argv[i], "--target_level") == 0) {
286 i++;
287 int level;
288 ASSERT_EQ(1, sscanf(argv[i], "%d", &level));
289
290 ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
291 ASSERT_EQ(apm->kNoError,
292 apm->gain_control()->set_target_level_dbfs(level));
293
294 } else if (strcmp(argv[i], "--compression_gain") == 0) {
295 i++;
296 int gain;
297 ASSERT_EQ(1, sscanf(argv[i], "%d", &gain));
298
299 ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
300 ASSERT_EQ(apm->kNoError,
301 apm->gain_control()->set_compression_gain_db(gain));
302
303 } else if (strcmp(argv[i], "--limiter") == 0) {
304 ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
305 ASSERT_EQ(apm->kNoError,
306 apm->gain_control()->enable_limiter(true));
307
308 } else if (strcmp(argv[i], "--no_limiter") == 0) {
309 ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
310 ASSERT_EQ(apm->kNoError,
311 apm->gain_control()->enable_limiter(false));
312
313 } else if (strcmp(argv[i], "-hpf") == 0) {
314 ASSERT_EQ(apm->kNoError, apm->high_pass_filter()->Enable(true));
315
316 } else if (strcmp(argv[i], "-ns") == 0) {
317 ASSERT_EQ(apm->kNoError, apm->noise_suppression()->Enable(true));
318
319 } else if (strcmp(argv[i], "--ns_low") == 0) {
320 ASSERT_EQ(apm->kNoError, apm->noise_suppression()->Enable(true));
321 ASSERT_EQ(apm->kNoError,
322 apm->noise_suppression()->set_level(NoiseSuppression::kLow));
323
324 } else if (strcmp(argv[i], "--ns_moderate") == 0) {
325 ASSERT_EQ(apm->kNoError, apm->noise_suppression()->Enable(true));
326 ASSERT_EQ(apm->kNoError,
327 apm->noise_suppression()->set_level(NoiseSuppression::kModerate));
328
329 } else if (strcmp(argv[i], "--ns_high") == 0) {
330 ASSERT_EQ(apm->kNoError, apm->noise_suppression()->Enable(true));
331 ASSERT_EQ(apm->kNoError,
332 apm->noise_suppression()->set_level(NoiseSuppression::kHigh));
333
334 } else if (strcmp(argv[i], "--ns_very_high") == 0) {
335 ASSERT_EQ(apm->kNoError, apm->noise_suppression()->Enable(true));
336 ASSERT_EQ(apm->kNoError,
337 apm->noise_suppression()->set_level(NoiseSuppression::kVeryHigh));
338
339 } else if (strcmp(argv[i], "-vad") == 0) {
340 ASSERT_EQ(apm->kNoError, apm->voice_detection()->Enable(true));
341
342 } else if (strcmp(argv[i], "--vad_out_file") == 0) {
343 i++;
344 ASSERT_LT(i, argc) << "Specify filename after --vad_out_file";
345 vad_out_filename = argv[i];
346
347 } else if (strcmp(argv[i], "--noasm") == 0) {
348 WebRtc_GetCPUInfo = WebRtc_GetCPUInfoNoASM;
349 // We need to reinitialize here if components have already been enabled.
350 ASSERT_EQ(apm->kNoError, apm->Initialize());
351
352 } else if (strcmp(argv[i], "--delay") == 0) {
353 i++;
354 ASSERT_EQ(1, sscanf(argv[i], "%d", &extra_delay_ms));
355
356 } else if (strcmp(argv[i], "--perf") == 0) {
357 perf_testing = true;
358
359 } else if (strcmp(argv[i], "--quiet") == 0) {
360 verbose = false;
361 progress = false;
362
363 } else if (strcmp(argv[i], "--no_progress") == 0) {
364 progress = false;
365
366 } else if (strcmp(argv[i], "--debug_file") == 0) {
367 i++;
368 ASSERT_LT(i, argc) << "Specify filename after --debug_file";
369 ASSERT_EQ(apm->kNoError, apm->StartDebugRecording(argv[i]));
370 } else {
371 FAIL() << "Unrecognized argument " << argv[i];
372 }
373 }
374 // If we're reading a protobuf file, ensure a simulation hasn't also
375 // been requested (which makes no sense...)
376 ASSERT_FALSE(pb_filename && simulating);
377
378 if (verbose) {
379 printf("Sample rate: %d Hz\n", sample_rate_hz);
380 printf("Primary channels: %d (in), %d (out)\n",
381 num_capture_input_channels,
382 num_capture_output_channels);
383 printf("Reverse channels: %d \n", num_render_channels);
384 }
385
386 const char far_file_default[] = "apm_far.pcm";
387 const char near_file_default[] = "apm_near.pcm";
388 const char out_file_default[] = "out.pcm";
389 const char event_filename[] = "apm_event.dat";
390 const char delay_filename[] = "apm_delay.dat";
391 const char drift_filename[] = "apm_drift.dat";
392 const char vad_file_default[] = "vad_out.dat";
393
394 if (!simulating) {
395 far_filename = far_file_default;
396 near_filename = near_file_default;
397 }
398
399 if (!out_filename) {
400 out_filename = out_file_default;
401 }
402
403 if (!vad_out_filename) {
404 vad_out_filename = vad_file_default;
405 }
406
407 FILE* pb_file = NULL;
408 FILE* far_file = NULL;
409 FILE* near_file = NULL;
410 FILE* out_file = NULL;
411 FILE* event_file = NULL;
412 FILE* delay_file = NULL;
413 FILE* drift_file = NULL;
414 FILE* vad_out_file = NULL;
415 FILE* aecm_echo_path_in_file = NULL;
416 FILE* aecm_echo_path_out_file = NULL;
417
418 if (pb_filename) {
419 pb_file = fopen(pb_filename, "rb");
420 ASSERT_TRUE(NULL != pb_file) << "Unable to open protobuf file "
421 << pb_filename;
422 } else {
423 if (far_filename) {
424 far_file = fopen(far_filename, "rb");
425 ASSERT_TRUE(NULL != far_file) << "Unable to open far-end audio file "
426 << far_filename;
427 }
428
429 near_file = fopen(near_filename, "rb");
430 ASSERT_TRUE(NULL != near_file) << "Unable to open near-end audio file "
431 << near_filename;
432 if (!simulating) {
433 event_file = fopen(event_filename, "rb");
434 ASSERT_TRUE(NULL != event_file) << "Unable to open event file "
435 << event_filename;
436
437 delay_file = fopen(delay_filename, "rb");
438 ASSERT_TRUE(NULL != delay_file) << "Unable to open buffer file "
439 << delay_filename;
440
441 drift_file = fopen(drift_filename, "rb");
442 ASSERT_TRUE(NULL != drift_file) << "Unable to open drift file "
443 << drift_filename;
444 }
445 }
446
447 out_file = fopen(out_filename, "wb");
448 ASSERT_TRUE(NULL != out_file) << "Unable to open output audio file "
449 << out_filename;
450
451 int near_size_bytes = 0;
452 if (pb_file) {
453 struct stat st;
454 stat(pb_filename, &st);
455 // Crude estimate, but should be good enough.
456 near_size_bytes = st.st_size / 3;
457 } else {
458 struct stat st;
459 stat(near_filename, &st);
460 near_size_bytes = st.st_size;
461 }
462
463 if (apm->voice_detection()->is_enabled()) {
464 vad_out_file = fopen(vad_out_filename, "wb");
465 ASSERT_TRUE(NULL != vad_out_file) << "Unable to open VAD output file "
466 << vad_out_file;
467 }
468
469 if (aecm_echo_path_in_filename != NULL) {
470 aecm_echo_path_in_file = fopen(aecm_echo_path_in_filename, "rb");
471 ASSERT_TRUE(NULL != aecm_echo_path_in_file) << "Unable to open file "
472 << aecm_echo_path_in_filename;
473
474 const size_t path_size =
475 apm->echo_control_mobile()->echo_path_size_bytes();
476 scoped_array<char> echo_path(new char[path_size]);
477 ASSERT_EQ(path_size, fread(echo_path.get(),
478 sizeof(char),
479 path_size,
480 aecm_echo_path_in_file));
481 EXPECT_EQ(apm->kNoError,
482 apm->echo_control_mobile()->SetEchoPath(echo_path.get(),
483 path_size));
484 fclose(aecm_echo_path_in_file);
485 aecm_echo_path_in_file = NULL;
486 }
487
488 if (aecm_echo_path_out_filename != NULL) {
489 aecm_echo_path_out_file = fopen(aecm_echo_path_out_filename, "wb");
490 ASSERT_TRUE(NULL != aecm_echo_path_out_file) << "Unable to open file "
491 << aecm_echo_path_out_filename;
492 }
493
494 size_t read_count = 0;
495 int reverse_count = 0;
496 int primary_count = 0;
497 int near_read_bytes = 0;
498 TickInterval acc_ticks;
499
500 AudioFrame far_frame;
501 AudioFrame near_frame;
502
503 int delay_ms = 0;
504 int drift_samples = 0;
505 int capture_level = 127;
506 int8_t stream_has_voice = 0;
507
508 TickTime t0 = TickTime::Now();
509 TickTime t1 = t0;
510 WebRtc_Word64 max_time_us = 0;
511 WebRtc_Word64 max_time_reverse_us = 0;
512 WebRtc_Word64 min_time_us = 1e6;
513 WebRtc_Word64 min_time_reverse_us = 1e6;
514
515 // TODO(ajm): Ideally we would refactor this block into separate functions,
516 // but for now we want to share the variables.
517 if (pb_file) {
518 Event event_msg;
519 while (ReadMessageFromFile(pb_file, &event_msg)) {
520 std::ostringstream trace_stream;
521 trace_stream << "Processed frames: " << reverse_count << " (reverse), "
522 << primary_count << " (primary)";
523 SCOPED_TRACE(trace_stream.str());
524
525 if (event_msg.type() == Event::INIT) {
526 ASSERT_TRUE(event_msg.has_init());
527 const Init msg = event_msg.init();
528
529 ASSERT_TRUE(msg.has_sample_rate());
530 ASSERT_EQ(apm->kNoError,
531 apm->set_sample_rate_hz(msg.sample_rate()));
532
533 ASSERT_TRUE(msg.has_device_sample_rate());
534 ASSERT_EQ(apm->kNoError,
535 apm->echo_cancellation()->set_device_sample_rate_hz(
536 msg.device_sample_rate()));
537
538 ASSERT_TRUE(msg.has_num_input_channels());
539 ASSERT_TRUE(msg.has_num_output_channels());
540 ASSERT_EQ(apm->kNoError,
541 apm->set_num_channels(msg.num_input_channels(),
542 msg.num_output_channels()));
543
544 ASSERT_TRUE(msg.has_num_reverse_channels());
545 ASSERT_EQ(apm->kNoError,
546 apm->set_num_reverse_channels(msg.num_reverse_channels()));
547
548 samples_per_channel = msg.sample_rate() / 100;
549 far_frame._frequencyInHz = msg.sample_rate();
550 far_frame._payloadDataLengthInSamples = samples_per_channel;
551 far_frame._audioChannel = msg.num_reverse_channels();
552 near_frame._frequencyInHz = msg.sample_rate();
553 near_frame._payloadDataLengthInSamples = samples_per_channel;
554
555 if (verbose) {
556 printf("Init at frame: %d (primary), %d (reverse)\n",
557 primary_count, reverse_count);
558 printf(" Sample rate: %d Hz\n", msg.sample_rate());
559 printf(" Primary channels: %d (in), %d (out)\n",
560 msg.num_input_channels(),
561 msg.num_output_channels());
562 printf(" Reverse channels: %d \n", msg.num_reverse_channels());
563 }
564
565 } else if (event_msg.type() == Event::REVERSE_STREAM) {
566 ASSERT_TRUE(event_msg.has_reverse_stream());
567 const ReverseStream msg = event_msg.reverse_stream();
568 reverse_count++;
569
570 ASSERT_TRUE(msg.has_data());
571 ASSERT_EQ(sizeof(int16_t) * samples_per_channel *
572 far_frame._audioChannel, msg.data().size());
573 memcpy(far_frame._payloadData, msg.data().data(), msg.data().size());
574
575 if (perf_testing) {
576 t0 = TickTime::Now();
577 }
578
579 ASSERT_EQ(apm->kNoError,
580 apm->AnalyzeReverseStream(&far_frame));
581
582 if (perf_testing) {
583 t1 = TickTime::Now();
584 TickInterval tick_diff = t1 - t0;
585 acc_ticks += tick_diff;
586 if (tick_diff.Microseconds() > max_time_reverse_us) {
587 max_time_reverse_us = tick_diff.Microseconds();
588 }
589 if (tick_diff.Microseconds() < min_time_reverse_us) {
590 min_time_reverse_us = tick_diff.Microseconds();
591 }
592 }
593
594 } else if (event_msg.type() == Event::STREAM) {
595 ASSERT_TRUE(event_msg.has_stream());
596 const Stream msg = event_msg.stream();
597 primary_count++;
598
599 // ProcessStream could have changed this for the output frame.
600 near_frame._audioChannel = apm->num_input_channels();
601
602 ASSERT_TRUE(msg.has_input_data());
603 ASSERT_EQ(sizeof(int16_t) * samples_per_channel *
604 near_frame._audioChannel, msg.input_data().size());
605 memcpy(near_frame._payloadData,
606 msg.input_data().data(),
607 msg.input_data().size());
608
609 near_read_bytes += msg.input_data().size();
610 if (progress && primary_count % 100 == 0) {
611 printf("%.0f%% complete\r",
612 (near_read_bytes * 100.0) / near_size_bytes);
613 fflush(stdout);
614 }
615
616 if (perf_testing) {
617 t0 = TickTime::Now();
618 }
619
620 ASSERT_EQ(apm->kNoError,
621 apm->gain_control()->set_stream_analog_level(msg.level()));
622 ASSERT_EQ(apm->kNoError,
623 apm->set_stream_delay_ms(msg.delay() + extra_delay_ms));
624 ASSERT_EQ(apm->kNoError,
625 apm->echo_cancellation()->set_stream_drift_samples(msg.drift()));
626
627 int err = apm->ProcessStream(&near_frame);
628 if (err == apm->kBadStreamParameterWarning) {
629 printf("Bad parameter warning. %s\n", trace_stream.str().c_str());
630 }
631 ASSERT_TRUE(err == apm->kNoError ||
632 err == apm->kBadStreamParameterWarning);
633 ASSERT_TRUE(near_frame._audioChannel == apm->num_output_channels());
634
635 capture_level = apm->gain_control()->stream_analog_level();
636
637 stream_has_voice =
638 static_cast<int8_t>(apm->voice_detection()->stream_has_voice());
639 if (vad_out_file != NULL) {
640 ASSERT_EQ(1u, fwrite(&stream_has_voice,
641 sizeof(stream_has_voice),
642 1,
643 vad_out_file));
644 }
645
646 if (apm->gain_control()->mode() != GainControl::kAdaptiveAnalog) {
647 ASSERT_EQ(msg.level(), capture_level);
648 }
649
650 if (perf_testing) {
651 t1 = TickTime::Now();
652 TickInterval tick_diff = t1 - t0;
653 acc_ticks += tick_diff;
654 if (tick_diff.Microseconds() > max_time_us) {
655 max_time_us = tick_diff.Microseconds();
656 }
657 if (tick_diff.Microseconds() < min_time_us) {
658 min_time_us = tick_diff.Microseconds();
659 }
660 }
661
662 size_t size = samples_per_channel * near_frame._audioChannel;
663 ASSERT_EQ(size, fwrite(near_frame._payloadData,
664 sizeof(int16_t),
665 size,
666 out_file));
667 }
668 }
669
670 ASSERT_TRUE(feof(pb_file));
671
672 } else {
673 enum Events {
674 kInitializeEvent,
675 kRenderEvent,
676 kCaptureEvent,
677 kResetEventDeprecated
678 };
679 int16_t event = 0;
680 while (simulating || feof(event_file) == 0) {
681 std::ostringstream trace_stream;
682 trace_stream << "Processed frames: " << reverse_count << " (reverse), "
683 << primary_count << " (primary)";
684 SCOPED_TRACE(trace_stream.str());
685
686 if (simulating) {
687 if (far_file == NULL) {
688 event = kCaptureEvent;
689 } else {
690 if (event == kRenderEvent) {
691 event = kCaptureEvent;
692 } else {
693 event = kRenderEvent;
694 }
695 }
696 } else {
697 read_count = fread(&event, sizeof(event), 1, event_file);
698 if (read_count != 1) {
699 break;
700 }
701 }
702
703 far_frame._frequencyInHz = sample_rate_hz;
704 far_frame._payloadDataLengthInSamples = samples_per_channel;
705 far_frame._audioChannel = num_render_channels;
706 near_frame._frequencyInHz = sample_rate_hz;
707 near_frame._payloadDataLengthInSamples = samples_per_channel;
708
709 if (event == kInitializeEvent || event == kResetEventDeprecated) {
710 ASSERT_EQ(1u,
711 fread(&sample_rate_hz, sizeof(sample_rate_hz), 1, event_file));
712 samples_per_channel = sample_rate_hz / 100;
713
714 ASSERT_EQ(1u,
715 fread(&device_sample_rate_hz,
716 sizeof(device_sample_rate_hz),
717 1,
718 event_file));
719
720 ASSERT_EQ(apm->kNoError,
721 apm->set_sample_rate_hz(sample_rate_hz));
722
723 ASSERT_EQ(apm->kNoError,
724 apm->echo_cancellation()->set_device_sample_rate_hz(
725 device_sample_rate_hz));
726
727 far_frame._frequencyInHz = sample_rate_hz;
728 far_frame._payloadDataLengthInSamples = samples_per_channel;
729 far_frame._audioChannel = num_render_channels;
730 near_frame._frequencyInHz = sample_rate_hz;
731 near_frame._payloadDataLengthInSamples = samples_per_channel;
732
733 if (verbose) {
734 printf("Init at frame: %d (primary), %d (reverse)\n",
735 primary_count, reverse_count);
736 printf(" Sample rate: %d Hz\n", sample_rate_hz);
737 }
738
739 } else if (event == kRenderEvent) {
740 reverse_count++;
741
742 size_t size = samples_per_channel * num_render_channels;
743 read_count = fread(far_frame._payloadData,
744 sizeof(int16_t),
745 size,
746 far_file);
747
748 if (simulating) {
749 if (read_count != size) {
750 // Read an equal amount from the near file to avoid errors due to
751 // not reaching end-of-file.
752 EXPECT_EQ(0, fseek(near_file, read_count * sizeof(int16_t),
753 SEEK_CUR));
754 break; // This is expected.
755 }
756 } else {
757 ASSERT_EQ(size, read_count);
758 }
759
760 if (perf_testing) {
761 t0 = TickTime::Now();
762 }
763
764 ASSERT_EQ(apm->kNoError,
765 apm->AnalyzeReverseStream(&far_frame));
766
767 if (perf_testing) {
768 t1 = TickTime::Now();
769 TickInterval tick_diff = t1 - t0;
770 acc_ticks += tick_diff;
771 if (tick_diff.Microseconds() > max_time_reverse_us) {
772 max_time_reverse_us = tick_diff.Microseconds();
773 }
774 if (tick_diff.Microseconds() < min_time_reverse_us) {
775 min_time_reverse_us = tick_diff.Microseconds();
776 }
777 }
778
779 } else if (event == kCaptureEvent) {
780 primary_count++;
781 near_frame._audioChannel = num_capture_input_channels;
782
783 size_t size = samples_per_channel * num_capture_input_channels;
784 read_count = fread(near_frame._payloadData,
785 sizeof(int16_t),
786 size,
787 near_file);
788
789 near_read_bytes += read_count * sizeof(int16_t);
790 if (progress && primary_count % 100 == 0) {
791 printf("%.0f%% complete\r",
792 (near_read_bytes * 100.0) / near_size_bytes);
793 fflush(stdout);
794 }
795 if (simulating) {
796 if (read_count != size) {
797 break; // This is expected.
798 }
799
800 delay_ms = 0;
801 drift_samples = 0;
802 } else {
803 ASSERT_EQ(size, read_count);
804
805 // TODO(ajm): sizeof(delay_ms) for current files?
806 ASSERT_EQ(1u,
807 fread(&delay_ms, 2, 1, delay_file));
808 ASSERT_EQ(1u,
809 fread(&drift_samples, sizeof(drift_samples), 1, drift_file));
810 }
811
812 if (perf_testing) {
813 t0 = TickTime::Now();
814 }
815
816 // TODO(ajm): fake an analog gain while simulating.
817
818 int capture_level_in = capture_level;
819 ASSERT_EQ(apm->kNoError,
820 apm->gain_control()->set_stream_analog_level(capture_level));
821 ASSERT_EQ(apm->kNoError,
822 apm->set_stream_delay_ms(delay_ms + extra_delay_ms));
823 ASSERT_EQ(apm->kNoError,
824 apm->echo_cancellation()->set_stream_drift_samples(drift_samples));
825
826 int err = apm->ProcessStream(&near_frame);
827 if (err == apm->kBadStreamParameterWarning) {
828 printf("Bad parameter warning. %s\n", trace_stream.str().c_str());
829 }
830 ASSERT_TRUE(err == apm->kNoError ||
831 err == apm->kBadStreamParameterWarning);
832 ASSERT_TRUE(near_frame._audioChannel == apm->num_output_channels());
833
834 capture_level = apm->gain_control()->stream_analog_level();
835
836 stream_has_voice =
837 static_cast<int8_t>(apm->voice_detection()->stream_has_voice());
838 if (vad_out_file != NULL) {
839 ASSERT_EQ(1u, fwrite(&stream_has_voice,
840 sizeof(stream_has_voice),
841 1,
842 vad_out_file));
843 }
844
845 if (apm->gain_control()->mode() != GainControl::kAdaptiveAnalog) {
846 ASSERT_EQ(capture_level_in, capture_level);
847 }
848
849 if (perf_testing) {
850 t1 = TickTime::Now();
851 TickInterval tick_diff = t1 - t0;
852 acc_ticks += tick_diff;
853 if (tick_diff.Microseconds() > max_time_us) {
854 max_time_us = tick_diff.Microseconds();
855 }
856 if (tick_diff.Microseconds() < min_time_us) {
857 min_time_us = tick_diff.Microseconds();
858 }
859 }
860
861 size = samples_per_channel * near_frame._audioChannel;
862 ASSERT_EQ(size, fwrite(near_frame._payloadData,
863 sizeof(int16_t),
864 size,
865 out_file));
866 }
867 else {
868 FAIL() << "Event " << event << " is unrecognized";
869 }
870 }
871 }
872 printf("100%% complete\r");
873
874 if (aecm_echo_path_out_file != NULL) {
875 const size_t path_size =
876 apm->echo_control_mobile()->echo_path_size_bytes();
877 scoped_array<char> echo_path(new char[path_size]);
878 apm->echo_control_mobile()->GetEchoPath(echo_path.get(), path_size);
879 ASSERT_EQ(path_size, fwrite(echo_path.get(),
880 sizeof(char),
881 path_size,
882 aecm_echo_path_out_file));
883 fclose(aecm_echo_path_out_file);
884 aecm_echo_path_out_file = NULL;
885 }
886
887 if (verbose) {
888 printf("\nProcessed frames: %d (primary), %d (reverse)\n",
889 primary_count, reverse_count);
890
891 if (apm->level_estimator()->is_enabled()) {
892 printf("\n--Level metrics--\n");
893 printf("RMS: %d dBFS\n", -apm->level_estimator()->RMS());
894 }
895 if (apm->echo_cancellation()->are_metrics_enabled()) {
896 EchoCancellation::Metrics metrics;
897 apm->echo_cancellation()->GetMetrics(&metrics);
898 printf("\n--Echo metrics--\n");
899 printf("(avg, max, min)\n");
900 printf("ERL: ");
901 PrintStat(metrics.echo_return_loss);
902 printf("ERLE: ");
903 PrintStat(metrics.echo_return_loss_enhancement);
904 printf("ANLP: ");
905 PrintStat(metrics.a_nlp);
906 }
907 if (apm->echo_cancellation()->is_delay_logging_enabled()) {
908 int median = 0;
909 int std = 0;
910 apm->echo_cancellation()->GetDelayMetrics(&median, &std);
911 printf("\n--Delay metrics--\n");
912 printf("Median: %3d\n", median);
913 printf("Standard deviation: %3d\n", std);
914 }
915 }
916
917 if (!pb_file) {
918 int8_t temp_int8;
919 if (far_file) {
920 read_count = fread(&temp_int8, sizeof(temp_int8), 1, far_file);
921 EXPECT_NE(0, feof(far_file)) << "Far-end file not fully processed";
922 }
923
924 read_count = fread(&temp_int8, sizeof(temp_int8), 1, near_file);
925 EXPECT_NE(0, feof(near_file)) << "Near-end file not fully processed";
926
927 if (!simulating) {
928 read_count = fread(&temp_int8, sizeof(temp_int8), 1, event_file);
929 EXPECT_NE(0, feof(event_file)) << "Event file not fully processed";
930 read_count = fread(&temp_int8, sizeof(temp_int8), 1, delay_file);
931 EXPECT_NE(0, feof(delay_file)) << "Delay file not fully processed";
932 read_count = fread(&temp_int8, sizeof(temp_int8), 1, drift_file);
933 EXPECT_NE(0, feof(drift_file)) << "Drift file not fully processed";
934 }
935 }
936
937 if (perf_testing) {
938 if (primary_count > 0) {
939 WebRtc_Word64 exec_time = acc_ticks.Milliseconds();
940 printf("\nTotal time: %.3f s, file time: %.2f s\n",
941 exec_time * 0.001, primary_count * 0.01);
942 printf("Time per frame: %.3f ms (average), %.3f ms (max),"
943 " %.3f ms (min)\n",
944 (exec_time * 1.0) / primary_count,
945 (max_time_us + max_time_reverse_us) / 1000.0,
946 (min_time_us + min_time_reverse_us) / 1000.0);
947 } else {
948 printf("Warning: no capture frames\n");
949 }
950 }
951
952 AudioProcessing::Destroy(apm);
953 apm = NULL;
954 }
955 } // namespace
956
main(int argc,char * argv[])957 int main(int argc, char* argv[])
958 {
959 void_main(argc, argv);
960
961 // Optional, but removes memory leak noise from Valgrind.
962 google::protobuf::ShutdownProtobufLibrary();
963 return 0;
964 }
965