2 * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
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.
20 #include "webrtc/common.h"
21 #include "webrtc/modules/audio_processing/include/audio_processing.h"
22 #include "webrtc/modules/audio_processing/test/test_utils.h"
23 #include "webrtc/modules/interface/module_common_types.h"
24 #include "webrtc/system_wrappers/interface/cpu_features_wrapper.h"
25 #include "webrtc/system_wrappers/interface/scoped_ptr.h"
26 #include "webrtc/system_wrappers/interface/tick_util.h"
27 #include "webrtc/test/testsupport/fileutils.h"
28 #include "webrtc/test/testsupport/perf_test.h"
29 #ifdef WEBRTC_ANDROID_PLATFORM_BUILD
30 #include "gtest/gtest.h"
31 #include "external/webrtc/webrtc/modules/audio_processing/debug.pb.h"
33 #include "testing/gtest/include/gtest/gtest.h"
34 #include "webrtc/audio_processing/debug.pb.h"
39 using webrtc::audioproc::Event;
40 using webrtc::audioproc::Init;
41 using webrtc::audioproc::ReverseStream;
42 using webrtc::audioproc::Stream;
46 void PrintStat(const AudioProcessing::Statistic& stat) {
47 printf("%d, %d, %d\n", stat.average,
54 "Usage: process_test [options] [-pb PROTOBUF_FILE]\n"
55 " [-ir REVERSE_FILE] [-i PRIMARY_FILE] [-o OUT_FILE]\n");
57 "process_test is a test application for AudioProcessing.\n\n"
58 "When a protobuf debug file is available, specify it with -pb. Alternately,\n"
59 "when -ir or -i is used, the specified files will be processed directly in\n"
60 "a simulation mode. Otherwise the full set of legacy test files is expected\n"
61 "to be present in the working directory. OUT_FILE should be specified\n"
62 "without extension to support both int and float output.\n\n");
64 printf("General configuration (only used for the simulation mode):\n");
65 printf(" -fs SAMPLE_RATE_HZ\n");
66 printf(" -ch CHANNELS_IN CHANNELS_OUT\n");
67 printf(" -rch REVERSE_CHANNELS\n");
69 printf("Component configuration:\n");
71 "All components are disabled by default. Each block below begins with a\n"
72 "flag to enable the component with default settings. The subsequent flags\n"
73 "in the block are used to provide configuration settings.\n");
74 printf("\n -aec Echo cancellation\n");
75 printf(" --drift_compensation\n");
76 printf(" --no_drift_compensation\n");
77 printf(" --no_echo_metrics\n");
78 printf(" --no_delay_logging\n");
79 printf(" --aec_suppression_level LEVEL [0 - 2]\n");
80 printf(" --extended_filter\n");
81 printf("\n -aecm Echo control mobile\n");
82 printf(" --aecm_echo_path_in_file FILE\n");
83 printf(" --aecm_echo_path_out_file FILE\n");
84 printf(" --no_comfort_noise\n");
85 printf(" --routing_mode MODE [0 - 4]\n");
86 printf("\n -agc Gain control\n");
87 printf(" --analog\n");
88 printf(" --adaptive_digital\n");
89 printf(" --fixed_digital\n");
90 printf(" --target_level LEVEL\n");
91 printf(" --compression_gain GAIN\n");
92 printf(" --limiter\n");
93 printf(" --no_limiter\n");
94 printf("\n -hpf High pass filter\n");
95 printf("\n -ns Noise suppression\n");
96 printf(" --ns_low\n");
97 printf(" --ns_moderate\n");
98 printf(" --ns_high\n");
99 printf(" --ns_very_high\n");
100 printf(" --ns_prob_file FILE\n");
101 printf("\n -vad Voice activity detection\n");
102 printf(" --vad_out_file FILE\n");
103 printf("\n -expns Experimental noise suppression\n");
104 printf("\n Level metrics (enabled by default)\n");
105 printf(" --no_level_metrics\n");
107 printf("Modifiers:\n");
108 printf(" --noasm Disable SSE optimization.\n");
109 printf(" --add_delay DELAY Add DELAY ms to input value.\n");
110 printf(" --delay DELAY Override input delay with DELAY ms.\n");
111 printf(" --perf Measure performance.\n");
112 printf(" --quiet Suppress text output.\n");
113 printf(" --no_progress Suppress progress.\n");
114 printf(" --debug_file FILE Dump a debug recording.\n");
117 static float MicLevel2Gain(int level) {
118 return pow(10.0f, ((level - 127.0f) / 128.0f * 40.0f) / 20.0f);
121 static void SimulateMic(int mic_level, AudioFrame* frame) {
122 mic_level = std::min(std::max(mic_level, 0), 255);
123 float mic_gain = MicLevel2Gain(mic_level);
124 int num_samples = frame->samples_per_channel_ * frame->num_channels_;
126 for (int n = 0; n < num_samples; n++) {
127 v = floor(frame->data_[n] * mic_gain + 0.5);
128 v = std::max(std::min(32767.0f, v), -32768.0f);
129 frame->data_[n] = static_cast<int16_t>(v);
133 // void function for gtest.
134 void void_main(int argc, char* argv[]) {
135 if (argc > 1 && strcmp(argv[1], "--help") == 0) {
141 printf("Did you mean to run without arguments?\n");
142 printf("Try `process_test --help' for more information.\n\n");
145 scoped_ptr<AudioProcessing> apm(AudioProcessing::Create());
146 ASSERT_TRUE(apm.get() != NULL);
148 const char* pb_filename = NULL;
149 const char* far_filename = NULL;
150 const char* near_filename = NULL;
151 std::string out_filename;
152 const char* vad_out_filename = NULL;
153 const char* ns_prob_filename = NULL;
154 const char* aecm_echo_path_in_filename = NULL;
155 const char* aecm_echo_path_out_filename = NULL;
157 int32_t sample_rate_hz = 16000;
159 int num_capture_input_channels = 1;
160 int num_capture_output_channels = 1;
161 int num_render_channels = 1;
163 int samples_per_channel = sample_rate_hz / 100;
165 bool simulating = false;
166 bool perf_testing = false;
168 bool progress = true;
169 int extra_delay_ms = 0;
170 int override_delay_ms = 0;
172 ASSERT_EQ(apm->kNoError, apm->level_estimator()->Enable(true));
173 for (int i = 1; i < argc; i++) {
174 if (strcmp(argv[i], "-pb") == 0) {
176 ASSERT_LT(i, argc) << "Specify protobuf filename after -pb";
177 pb_filename = argv[i];
179 } else if (strcmp(argv[i], "-ir") == 0) {
181 ASSERT_LT(i, argc) << "Specify filename after -ir";
182 far_filename = argv[i];
185 } else if (strcmp(argv[i], "-i") == 0) {
187 ASSERT_LT(i, argc) << "Specify filename after -i";
188 near_filename = argv[i];
191 } else if (strcmp(argv[i], "-o") == 0) {
193 ASSERT_LT(i, argc) << "Specify filename without extension after -o";
194 out_filename = argv[i];
196 } else if (strcmp(argv[i], "-fs") == 0) {
198 ASSERT_LT(i, argc) << "Specify sample rate after -fs";
199 ASSERT_EQ(1, sscanf(argv[i], "%d", &sample_rate_hz));
200 samples_per_channel = sample_rate_hz / 100;
202 } else if (strcmp(argv[i], "-ch") == 0) {
204 ASSERT_LT(i + 1, argc) << "Specify number of channels after -ch";
205 ASSERT_EQ(1, sscanf(argv[i], "%d", &num_capture_input_channels));
207 ASSERT_EQ(1, sscanf(argv[i], "%d", &num_capture_output_channels));
209 } else if (strcmp(argv[i], "-rch") == 0) {
211 ASSERT_LT(i, argc) << "Specify number of channels after -rch";
212 ASSERT_EQ(1, sscanf(argv[i], "%d", &num_render_channels));
214 } else if (strcmp(argv[i], "-aec") == 0) {
215 ASSERT_EQ(apm->kNoError, apm->echo_cancellation()->Enable(true));
216 ASSERT_EQ(apm->kNoError,
217 apm->echo_cancellation()->enable_metrics(true));
218 ASSERT_EQ(apm->kNoError,
219 apm->echo_cancellation()->enable_delay_logging(true));
221 } else if (strcmp(argv[i], "--drift_compensation") == 0) {
222 ASSERT_EQ(apm->kNoError, apm->echo_cancellation()->Enable(true));
223 // TODO(ajm): this is enabled in the VQE test app by default. Investigate
224 // why it can give better performance despite passing zeros.
225 ASSERT_EQ(apm->kNoError,
226 apm->echo_cancellation()->enable_drift_compensation(true));
227 } else if (strcmp(argv[i], "--no_drift_compensation") == 0) {
228 ASSERT_EQ(apm->kNoError, apm->echo_cancellation()->Enable(true));
229 ASSERT_EQ(apm->kNoError,
230 apm->echo_cancellation()->enable_drift_compensation(false));
232 } else if (strcmp(argv[i], "--no_echo_metrics") == 0) {
233 ASSERT_EQ(apm->kNoError, apm->echo_cancellation()->Enable(true));
234 ASSERT_EQ(apm->kNoError,
235 apm->echo_cancellation()->enable_metrics(false));
237 } else if (strcmp(argv[i], "--no_delay_logging") == 0) {
238 ASSERT_EQ(apm->kNoError, apm->echo_cancellation()->Enable(true));
239 ASSERT_EQ(apm->kNoError,
240 apm->echo_cancellation()->enable_delay_logging(false));
242 } else if (strcmp(argv[i], "--no_level_metrics") == 0) {
243 ASSERT_EQ(apm->kNoError, apm->level_estimator()->Enable(false));
245 } else if (strcmp(argv[i], "--aec_suppression_level") == 0) {
247 ASSERT_LT(i, argc) << "Specify level after --aec_suppression_level";
248 int suppression_level;
249 ASSERT_EQ(1, sscanf(argv[i], "%d", &suppression_level));
250 ASSERT_EQ(apm->kNoError,
251 apm->echo_cancellation()->set_suppression_level(
252 static_cast<webrtc::EchoCancellation::SuppressionLevel>(
253 suppression_level)));
255 } else if (strcmp(argv[i], "--extended_filter") == 0) {
257 config.Set<DelayCorrection>(new DelayCorrection(true));
258 apm->SetExtraOptions(config);
260 } else if (strcmp(argv[i], "-aecm") == 0) {
261 ASSERT_EQ(apm->kNoError, apm->echo_control_mobile()->Enable(true));
263 } else if (strcmp(argv[i], "--aecm_echo_path_in_file") == 0) {
265 ASSERT_LT(i, argc) << "Specify filename after --aecm_echo_path_in_file";
266 aecm_echo_path_in_filename = argv[i];
268 } else if (strcmp(argv[i], "--aecm_echo_path_out_file") == 0) {
270 ASSERT_LT(i, argc) << "Specify filename after --aecm_echo_path_out_file";
271 aecm_echo_path_out_filename = argv[i];
273 } else if (strcmp(argv[i], "--no_comfort_noise") == 0) {
274 ASSERT_EQ(apm->kNoError,
275 apm->echo_control_mobile()->enable_comfort_noise(false));
277 } else if (strcmp(argv[i], "--routing_mode") == 0) {
279 ASSERT_LT(i, argc) << "Specify mode after --routing_mode";
281 ASSERT_EQ(1, sscanf(argv[i], "%d", &routing_mode));
282 ASSERT_EQ(apm->kNoError,
283 apm->echo_control_mobile()->set_routing_mode(
284 static_cast<webrtc::EchoControlMobile::RoutingMode>(
287 } else if (strcmp(argv[i], "-agc") == 0) {
288 ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
290 } else if (strcmp(argv[i], "--analog") == 0) {
291 ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
292 ASSERT_EQ(apm->kNoError,
293 apm->gain_control()->set_mode(GainControl::kAdaptiveAnalog));
295 } else if (strcmp(argv[i], "--adaptive_digital") == 0) {
296 ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
297 ASSERT_EQ(apm->kNoError,
298 apm->gain_control()->set_mode(GainControl::kAdaptiveDigital));
300 } else if (strcmp(argv[i], "--fixed_digital") == 0) {
301 ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
302 ASSERT_EQ(apm->kNoError,
303 apm->gain_control()->set_mode(GainControl::kFixedDigital));
305 } else if (strcmp(argv[i], "--target_level") == 0) {
308 ASSERT_EQ(1, sscanf(argv[i], "%d", &level));
310 ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
311 ASSERT_EQ(apm->kNoError,
312 apm->gain_control()->set_target_level_dbfs(level));
314 } else if (strcmp(argv[i], "--compression_gain") == 0) {
317 ASSERT_EQ(1, sscanf(argv[i], "%d", &gain));
319 ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
320 ASSERT_EQ(apm->kNoError,
321 apm->gain_control()->set_compression_gain_db(gain));
323 } else if (strcmp(argv[i], "--limiter") == 0) {
324 ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
325 ASSERT_EQ(apm->kNoError,
326 apm->gain_control()->enable_limiter(true));
328 } else if (strcmp(argv[i], "--no_limiter") == 0) {
329 ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
330 ASSERT_EQ(apm->kNoError,
331 apm->gain_control()->enable_limiter(false));
333 } else if (strcmp(argv[i], "-hpf") == 0) {
334 ASSERT_EQ(apm->kNoError, apm->high_pass_filter()->Enable(true));
336 } else if (strcmp(argv[i], "-ns") == 0) {
337 ASSERT_EQ(apm->kNoError, apm->noise_suppression()->Enable(true));
339 } else if (strcmp(argv[i], "--ns_low") == 0) {
340 ASSERT_EQ(apm->kNoError, apm->noise_suppression()->Enable(true));
341 ASSERT_EQ(apm->kNoError,
342 apm->noise_suppression()->set_level(NoiseSuppression::kLow));
344 } else if (strcmp(argv[i], "--ns_moderate") == 0) {
345 ASSERT_EQ(apm->kNoError, apm->noise_suppression()->Enable(true));
346 ASSERT_EQ(apm->kNoError,
347 apm->noise_suppression()->set_level(NoiseSuppression::kModerate));
349 } else if (strcmp(argv[i], "--ns_high") == 0) {
350 ASSERT_EQ(apm->kNoError, apm->noise_suppression()->Enable(true));
351 ASSERT_EQ(apm->kNoError,
352 apm->noise_suppression()->set_level(NoiseSuppression::kHigh));
354 } else if (strcmp(argv[i], "--ns_very_high") == 0) {
355 ASSERT_EQ(apm->kNoError, apm->noise_suppression()->Enable(true));
356 ASSERT_EQ(apm->kNoError,
357 apm->noise_suppression()->set_level(NoiseSuppression::kVeryHigh));
359 } else if (strcmp(argv[i], "--ns_prob_file") == 0) {
361 ASSERT_LT(i, argc) << "Specify filename after --ns_prob_file";
362 ns_prob_filename = argv[i];
364 } else if (strcmp(argv[i], "-vad") == 0) {
365 ASSERT_EQ(apm->kNoError, apm->voice_detection()->Enable(true));
367 } else if (strcmp(argv[i], "--vad_very_low") == 0) {
368 ASSERT_EQ(apm->kNoError, apm->voice_detection()->Enable(true));
369 ASSERT_EQ(apm->kNoError,
370 apm->voice_detection()->set_likelihood(
371 VoiceDetection::kVeryLowLikelihood));
373 } else if (strcmp(argv[i], "--vad_low") == 0) {
374 ASSERT_EQ(apm->kNoError, apm->voice_detection()->Enable(true));
375 ASSERT_EQ(apm->kNoError,
376 apm->voice_detection()->set_likelihood(
377 VoiceDetection::kLowLikelihood));
379 } else if (strcmp(argv[i], "--vad_moderate") == 0) {
380 ASSERT_EQ(apm->kNoError, apm->voice_detection()->Enable(true));
381 ASSERT_EQ(apm->kNoError,
382 apm->voice_detection()->set_likelihood(
383 VoiceDetection::kModerateLikelihood));
385 } else if (strcmp(argv[i], "--vad_high") == 0) {
386 ASSERT_EQ(apm->kNoError, apm->voice_detection()->Enable(true));
387 ASSERT_EQ(apm->kNoError,
388 apm->voice_detection()->set_likelihood(
389 VoiceDetection::kHighLikelihood));
391 } else if (strcmp(argv[i], "--vad_out_file") == 0) {
393 ASSERT_LT(i, argc) << "Specify filename after --vad_out_file";
394 vad_out_filename = argv[i];
396 } else if (strcmp(argv[i], "-expns") == 0) {
397 ASSERT_EQ(apm->kNoError, apm->EnableExperimentalNs(true));
399 } else if (strcmp(argv[i], "--noasm") == 0) {
400 WebRtc_GetCPUInfo = WebRtc_GetCPUInfoNoASM;
401 // We need to reinitialize here if components have already been enabled.
402 ASSERT_EQ(apm->kNoError, apm->Initialize());
404 } else if (strcmp(argv[i], "--add_delay") == 0) {
406 ASSERT_EQ(1, sscanf(argv[i], "%d", &extra_delay_ms));
408 } else if (strcmp(argv[i], "--delay") == 0) {
410 ASSERT_EQ(1, sscanf(argv[i], "%d", &override_delay_ms));
412 } else if (strcmp(argv[i], "--perf") == 0) {
415 } else if (strcmp(argv[i], "--quiet") == 0) {
419 } else if (strcmp(argv[i], "--no_progress") == 0) {
422 } else if (strcmp(argv[i], "--debug_file") == 0) {
424 ASSERT_LT(i, argc) << "Specify filename after --debug_file";
425 ASSERT_EQ(apm->kNoError, apm->StartDebugRecording(argv[i]));
427 FAIL() << "Unrecognized argument " << argv[i];
430 // If we're reading a protobuf file, ensure a simulation hasn't also
431 // been requested (which makes no sense...)
432 ASSERT_FALSE(pb_filename && simulating);
435 printf("Sample rate: %d Hz\n", sample_rate_hz);
436 printf("Primary channels: %d (in), %d (out)\n",
437 num_capture_input_channels,
438 num_capture_output_channels);
439 printf("Reverse channels: %d \n", num_render_channels);
442 const std::string out_path = webrtc::test::OutputPath();
443 const char far_file_default[] = "apm_far.pcm";
444 const char near_file_default[] = "apm_near.pcm";
445 const char event_filename[] = "apm_event.dat";
446 const char delay_filename[] = "apm_delay.dat";
447 const char drift_filename[] = "apm_drift.dat";
448 const std::string vad_file_default = out_path + "vad_out.dat";
449 const std::string ns_prob_file_default = out_path + "ns_prob.dat";
452 far_filename = far_file_default;
453 near_filename = near_file_default;
456 if (out_filename.size() == 0) {
457 out_filename = out_path + "out";
459 std::string out_float_filename = out_filename + ".float";
460 out_filename += ".pcm";
462 if (!vad_out_filename) {
463 vad_out_filename = vad_file_default.c_str();
466 if (!ns_prob_filename) {
467 ns_prob_filename = ns_prob_file_default.c_str();
470 FILE* pb_file = NULL;
471 FILE* far_file = NULL;
472 FILE* near_file = NULL;
473 FILE* event_file = NULL;
474 FILE* delay_file = NULL;
475 FILE* drift_file = NULL;
476 FILE* vad_out_file = NULL;
477 FILE* ns_prob_file = NULL;
478 FILE* aecm_echo_path_in_file = NULL;
479 FILE* aecm_echo_path_out_file = NULL;
482 pb_file = OpenFile(pb_filename, "rb");
485 far_file = OpenFile(far_filename, "rb");
488 near_file = OpenFile(near_filename, "rb");
490 event_file = OpenFile(event_filename, "rb");
491 delay_file = OpenFile(delay_filename, "rb");
492 drift_file = OpenFile(drift_filename, "rb");
496 int near_size_bytes = 0;
499 stat(pb_filename, &st);
500 // Crude estimate, but should be good enough.
501 near_size_bytes = st.st_size / 3;
504 stat(near_filename, &st);
505 near_size_bytes = st.st_size;
508 if (apm->voice_detection()->is_enabled()) {
509 vad_out_file = OpenFile(vad_out_filename, "wb");
512 if (apm->noise_suppression()->is_enabled()) {
513 ns_prob_file = OpenFile(ns_prob_filename, "wb");
516 if (aecm_echo_path_in_filename != NULL) {
517 aecm_echo_path_in_file = OpenFile(aecm_echo_path_in_filename, "rb");
519 const size_t path_size =
520 apm->echo_control_mobile()->echo_path_size_bytes();
521 scoped_ptr<char[]> echo_path(new char[path_size]);
522 ASSERT_EQ(path_size, fread(echo_path.get(),
525 aecm_echo_path_in_file));
526 EXPECT_EQ(apm->kNoError,
527 apm->echo_control_mobile()->SetEchoPath(echo_path.get(),
529 fclose(aecm_echo_path_in_file);
530 aecm_echo_path_in_file = NULL;
533 if (aecm_echo_path_out_filename != NULL) {
534 aecm_echo_path_out_file = OpenFile(aecm_echo_path_out_filename, "wb");
537 size_t read_count = 0;
538 int reverse_count = 0;
539 int primary_count = 0;
540 int near_read_bytes = 0;
541 TickInterval acc_ticks;
543 AudioFrame far_frame;
544 AudioFrame near_frame;
547 int drift_samples = 0;
548 int capture_level = 127;
549 int8_t stream_has_voice = 0;
550 float ns_speech_prob = 0.0f;
552 TickTime t0 = TickTime::Now();
554 int64_t max_time_us = 0;
555 int64_t max_time_reverse_us = 0;
556 int64_t min_time_us = 1e6;
557 int64_t min_time_reverse_us = 1e6;
559 // TODO(ajm): Ideally we would refactor this block into separate functions,
560 // but for now we want to share the variables.
563 scoped_ptr<ChannelBuffer<float> > reverse_cb;
564 scoped_ptr<ChannelBuffer<float> > primary_cb;
565 int output_sample_rate = 32000;
566 AudioProcessing::ChannelLayout output_layout = AudioProcessing::kMono;
567 while (ReadMessageFromFile(pb_file, &event_msg)) {
568 std::ostringstream trace_stream;
569 trace_stream << "Processed frames: " << reverse_count << " (reverse), "
570 << primary_count << " (primary)";
571 SCOPED_TRACE(trace_stream.str());
573 if (event_msg.type() == Event::INIT) {
574 ASSERT_TRUE(event_msg.has_init());
575 const Init msg = event_msg.init();
577 ASSERT_TRUE(msg.has_sample_rate());
578 ASSERT_TRUE(msg.has_num_input_channels());
579 ASSERT_TRUE(msg.has_num_output_channels());
580 ASSERT_TRUE(msg.has_num_reverse_channels());
581 int reverse_sample_rate = msg.sample_rate();
582 if (msg.has_reverse_sample_rate()) {
583 reverse_sample_rate = msg.reverse_sample_rate();
585 output_sample_rate = msg.sample_rate();
586 if (msg.has_output_sample_rate()) {
587 output_sample_rate = msg.output_sample_rate();
589 output_layout = LayoutFromChannels(msg.num_output_channels());
590 ASSERT_EQ(kNoErr, apm->Initialize(
594 LayoutFromChannels(msg.num_input_channels()),
596 LayoutFromChannels(msg.num_reverse_channels())));
598 samples_per_channel = msg.sample_rate() / 100;
599 far_frame.sample_rate_hz_ = msg.sample_rate();
600 far_frame.samples_per_channel_ = samples_per_channel;
601 far_frame.num_channels_ = msg.num_reverse_channels();
602 near_frame.sample_rate_hz_ = msg.sample_rate();
603 near_frame.samples_per_channel_ = samples_per_channel;
604 near_frame.num_channels_ = msg.num_input_channels();
605 reverse_cb.reset(new ChannelBuffer<float>(samples_per_channel,
606 msg.num_reverse_channels()));
607 primary_cb.reset(new ChannelBuffer<float>(samples_per_channel,
608 msg.num_input_channels()));
611 printf("Init at frame: %d (primary), %d (reverse)\n",
612 primary_count, reverse_count);
613 printf(" Primary rates: %d Hz (in), %d Hz (out)\n",
614 msg.sample_rate(), output_sample_rate);
615 printf(" Primary channels: %d (in), %d (out)\n",
616 msg.num_input_channels(),
617 msg.num_output_channels());
618 printf(" Reverse rate: %d\n", reverse_sample_rate);
619 printf(" Reverse channels: %d\n", msg.num_reverse_channels());
622 } else if (event_msg.type() == Event::REVERSE_STREAM) {
623 ASSERT_TRUE(event_msg.has_reverse_stream());
624 ReverseStream msg = event_msg.reverse_stream();
627 ASSERT_TRUE(msg.has_data() ^ (msg.channel_size() > 0));
628 if (msg.has_data()) {
629 ASSERT_EQ(sizeof(int16_t) * samples_per_channel *
630 far_frame.num_channels_, msg.data().size());
631 memcpy(far_frame.data_, msg.data().data(), msg.data().size());
633 for (int i = 0; i < msg.channel_size(); ++i) {
634 reverse_cb->CopyFrom(msg.channel(i).data(), i);
639 t0 = TickTime::Now();
642 if (msg.has_data()) {
643 ASSERT_EQ(apm->kNoError,
644 apm->AnalyzeReverseStream(&far_frame));
646 ASSERT_EQ(apm->kNoError,
647 apm->AnalyzeReverseStream(
648 reverse_cb->channels(),
649 far_frame.samples_per_channel_,
650 far_frame.sample_rate_hz_,
651 LayoutFromChannels(far_frame.num_channels_)));
655 t1 = TickTime::Now();
656 TickInterval tick_diff = t1 - t0;
657 acc_ticks += tick_diff;
658 if (tick_diff.Microseconds() > max_time_reverse_us) {
659 max_time_reverse_us = tick_diff.Microseconds();
661 if (tick_diff.Microseconds() < min_time_reverse_us) {
662 min_time_reverse_us = tick_diff.Microseconds();
666 } else if (event_msg.type() == Event::STREAM) {
667 ASSERT_TRUE(event_msg.has_stream());
668 const Stream msg = event_msg.stream();
671 // ProcessStream could have changed this for the output frame.
672 near_frame.num_channels_ = apm->num_input_channels();
674 ASSERT_TRUE(msg.has_input_data() ^ (msg.input_channel_size() > 0));
675 if (msg.has_input_data()) {
676 ASSERT_EQ(sizeof(int16_t) * samples_per_channel *
677 near_frame.num_channels_, msg.input_data().size());
678 memcpy(near_frame.data_,
679 msg.input_data().data(),
680 msg.input_data().size());
682 for (int i = 0; i < msg.input_channel_size(); ++i) {
683 primary_cb->CopyFrom(msg.input_channel(i).data(), i);
687 near_read_bytes += msg.input_data().size();
688 if (progress && primary_count % 100 == 0) {
689 printf("%.0f%% complete\r",
690 (near_read_bytes * 100.0) / near_size_bytes);
695 t0 = TickTime::Now();
698 ASSERT_EQ(apm->kNoError,
699 apm->gain_control()->set_stream_analog_level(msg.level()));
700 delay_ms = msg.delay() + extra_delay_ms;
701 if (override_delay_ms) {
702 delay_ms = override_delay_ms;
704 ASSERT_EQ(apm->kNoError,
705 apm->set_stream_delay_ms(delay_ms));
706 apm->echo_cancellation()->set_stream_drift_samples(msg.drift());
708 if (msg.has_keypress()) {
709 apm->set_stream_key_pressed(msg.keypress());
711 apm->set_stream_key_pressed(true);
714 int err = apm->kNoError;
715 if (msg.has_input_data()) {
716 err = apm->ProcessStream(&near_frame);
717 ASSERT_TRUE(near_frame.num_channels_ == apm->num_output_channels());
719 err = apm->ProcessStream(
720 primary_cb->channels(),
721 near_frame.samples_per_channel_,
722 near_frame.sample_rate_hz_,
723 LayoutFromChannels(near_frame.num_channels_),
726 primary_cb->channels());
729 if (err == apm->kBadStreamParameterWarning) {
730 printf("Bad parameter warning. %s\n", trace_stream.str().c_str());
732 ASSERT_TRUE(err == apm->kNoError ||
733 err == apm->kBadStreamParameterWarning);
736 static_cast<int8_t>(apm->voice_detection()->stream_has_voice());
737 if (vad_out_file != NULL) {
738 ASSERT_EQ(1u, fwrite(&stream_has_voice,
739 sizeof(stream_has_voice),
744 if (ns_prob_file != NULL) {
745 ns_speech_prob = apm->noise_suppression()->speech_probability();
746 ASSERT_EQ(1u, fwrite(&ns_speech_prob,
747 sizeof(ns_speech_prob),
753 t1 = TickTime::Now();
754 TickInterval tick_diff = t1 - t0;
755 acc_ticks += tick_diff;
756 if (tick_diff.Microseconds() > max_time_us) {
757 max_time_us = tick_diff.Microseconds();
759 if (tick_diff.Microseconds() < min_time_us) {
760 min_time_us = tick_diff.Microseconds();
764 size_t num_samples = samples_per_channel * apm->num_output_channels();
765 if (msg.has_input_data()) {
766 static FILE* out_file = OpenFile(out_filename, "wb");
767 ASSERT_EQ(num_samples, fwrite(near_frame.data_,
768 sizeof(*near_frame.data_),
772 static FILE* out_float_file = OpenFile(out_float_filename, "wb");
773 ASSERT_EQ(num_samples, fwrite(primary_cb->data(),
774 sizeof(*primary_cb->data()),
781 ASSERT_TRUE(feof(pb_file));
788 kResetEventDeprecated
791 while (simulating || feof(event_file) == 0) {
792 std::ostringstream trace_stream;
793 trace_stream << "Processed frames: " << reverse_count << " (reverse), "
794 << primary_count << " (primary)";
795 SCOPED_TRACE(trace_stream.str());
798 if (far_file == NULL) {
799 event = kCaptureEvent;
801 if (event == kRenderEvent) {
802 event = kCaptureEvent;
804 event = kRenderEvent;
808 read_count = fread(&event, sizeof(event), 1, event_file);
809 if (read_count != 1) {
814 far_frame.sample_rate_hz_ = sample_rate_hz;
815 far_frame.samples_per_channel_ = samples_per_channel;
816 far_frame.num_channels_ = num_render_channels;
817 near_frame.sample_rate_hz_ = sample_rate_hz;
818 near_frame.samples_per_channel_ = samples_per_channel;
820 if (event == kInitializeEvent || event == kResetEventDeprecated) {
822 fread(&sample_rate_hz, sizeof(sample_rate_hz), 1, event_file));
823 samples_per_channel = sample_rate_hz / 100;
825 int32_t unused_device_sample_rate_hz;
827 fread(&unused_device_sample_rate_hz,
828 sizeof(unused_device_sample_rate_hz),
832 ASSERT_EQ(kNoErr, apm->Initialize(
836 LayoutFromChannels(num_capture_input_channels),
837 LayoutFromChannels(num_capture_output_channels),
838 LayoutFromChannels(num_render_channels)));
840 far_frame.sample_rate_hz_ = sample_rate_hz;
841 far_frame.samples_per_channel_ = samples_per_channel;
842 far_frame.num_channels_ = num_render_channels;
843 near_frame.sample_rate_hz_ = sample_rate_hz;
844 near_frame.samples_per_channel_ = samples_per_channel;
847 printf("Init at frame: %d (primary), %d (reverse)\n",
848 primary_count, reverse_count);
849 printf(" Sample rate: %d Hz\n", sample_rate_hz);
852 } else if (event == kRenderEvent) {
855 size_t size = samples_per_channel * num_render_channels;
856 read_count = fread(far_frame.data_,
862 if (read_count != size) {
863 // Read an equal amount from the near file to avoid errors due to
864 // not reaching end-of-file.
865 EXPECT_EQ(0, fseek(near_file, read_count * sizeof(int16_t),
867 break; // This is expected.
870 ASSERT_EQ(size, read_count);
874 t0 = TickTime::Now();
877 ASSERT_EQ(apm->kNoError,
878 apm->AnalyzeReverseStream(&far_frame));
881 t1 = TickTime::Now();
882 TickInterval tick_diff = t1 - t0;
883 acc_ticks += tick_diff;
884 if (tick_diff.Microseconds() > max_time_reverse_us) {
885 max_time_reverse_us = tick_diff.Microseconds();
887 if (tick_diff.Microseconds() < min_time_reverse_us) {
888 min_time_reverse_us = tick_diff.Microseconds();
892 } else if (event == kCaptureEvent) {
894 near_frame.num_channels_ = num_capture_input_channels;
896 size_t size = samples_per_channel * num_capture_input_channels;
897 read_count = fread(near_frame.data_,
902 near_read_bytes += read_count * sizeof(int16_t);
903 if (progress && primary_count % 100 == 0) {
904 printf("%.0f%% complete\r",
905 (near_read_bytes * 100.0) / near_size_bytes);
909 if (read_count != size) {
910 break; // This is expected.
916 ASSERT_EQ(size, read_count);
918 // TODO(ajm): sizeof(delay_ms) for current files?
920 fread(&delay_ms, 2, 1, delay_file));
922 fread(&drift_samples, sizeof(drift_samples), 1, drift_file));
925 if (apm->gain_control()->is_enabled() &&
926 apm->gain_control()->mode() == GainControl::kAdaptiveAnalog) {
927 SimulateMic(capture_level, &near_frame);
931 t0 = TickTime::Now();
934 const int capture_level_in = capture_level;
935 ASSERT_EQ(apm->kNoError,
936 apm->gain_control()->set_stream_analog_level(capture_level));
937 delay_ms += extra_delay_ms;
938 if (override_delay_ms) {
939 delay_ms = override_delay_ms;
941 ASSERT_EQ(apm->kNoError,
942 apm->set_stream_delay_ms(delay_ms));
943 apm->echo_cancellation()->set_stream_drift_samples(drift_samples);
945 apm->set_stream_key_pressed(true);
947 int err = apm->ProcessStream(&near_frame);
948 if (err == apm->kBadStreamParameterWarning) {
949 printf("Bad parameter warning. %s\n", trace_stream.str().c_str());
951 ASSERT_TRUE(err == apm->kNoError ||
952 err == apm->kBadStreamParameterWarning);
953 ASSERT_TRUE(near_frame.num_channels_ == apm->num_output_channels());
955 capture_level = apm->gain_control()->stream_analog_level();
958 static_cast<int8_t>(apm->voice_detection()->stream_has_voice());
959 if (vad_out_file != NULL) {
960 ASSERT_EQ(1u, fwrite(&stream_has_voice,
961 sizeof(stream_has_voice),
966 if (ns_prob_file != NULL) {
967 ns_speech_prob = apm->noise_suppression()->speech_probability();
968 ASSERT_EQ(1u, fwrite(&ns_speech_prob,
969 sizeof(ns_speech_prob),
974 if (apm->gain_control()->mode() != GainControl::kAdaptiveAnalog) {
975 ASSERT_EQ(capture_level_in, capture_level);
979 t1 = TickTime::Now();
980 TickInterval tick_diff = t1 - t0;
981 acc_ticks += tick_diff;
982 if (tick_diff.Microseconds() > max_time_us) {
983 max_time_us = tick_diff.Microseconds();
985 if (tick_diff.Microseconds() < min_time_us) {
986 min_time_us = tick_diff.Microseconds();
990 size = samples_per_channel * near_frame.num_channels_;
991 static FILE* out_file = OpenFile(out_filename, "wb");
992 ASSERT_EQ(size, fwrite(near_frame.data_,
998 FAIL() << "Event " << event << " is unrecognized";
1002 printf("100%% complete\r");
1004 if (aecm_echo_path_out_file != NULL) {
1005 const size_t path_size =
1006 apm->echo_control_mobile()->echo_path_size_bytes();
1007 scoped_ptr<char[]> echo_path(new char[path_size]);
1008 apm->echo_control_mobile()->GetEchoPath(echo_path.get(), path_size);
1009 ASSERT_EQ(path_size, fwrite(echo_path.get(),
1012 aecm_echo_path_out_file));
1013 fclose(aecm_echo_path_out_file);
1014 aecm_echo_path_out_file = NULL;
1018 printf("\nProcessed frames: %d (primary), %d (reverse)\n",
1019 primary_count, reverse_count);
1021 if (apm->level_estimator()->is_enabled()) {
1022 printf("\n--Level metrics--\n");
1023 printf("RMS: %d dBFS\n", -apm->level_estimator()->RMS());
1025 if (apm->echo_cancellation()->are_metrics_enabled()) {
1026 EchoCancellation::Metrics metrics;
1027 apm->echo_cancellation()->GetMetrics(&metrics);
1028 printf("\n--Echo metrics--\n");
1029 printf("(avg, max, min)\n");
1031 PrintStat(metrics.echo_return_loss);
1033 PrintStat(metrics.echo_return_loss_enhancement);
1035 PrintStat(metrics.a_nlp);
1037 if (apm->echo_cancellation()->is_delay_logging_enabled()) {
1040 apm->echo_cancellation()->GetDelayMetrics(&median, &std);
1041 printf("\n--Delay metrics--\n");
1042 printf("Median: %3d\n", median);
1043 printf("Standard deviation: %3d\n", std);
1050 read_count = fread(&temp_int8, sizeof(temp_int8), 1, far_file);
1051 EXPECT_NE(0, feof(far_file)) << "Far-end file not fully processed";
1054 read_count = fread(&temp_int8, sizeof(temp_int8), 1, near_file);
1055 EXPECT_NE(0, feof(near_file)) << "Near-end file not fully processed";
1058 read_count = fread(&temp_int8, sizeof(temp_int8), 1, event_file);
1059 EXPECT_NE(0, feof(event_file)) << "Event file not fully processed";
1060 read_count = fread(&temp_int8, sizeof(temp_int8), 1, delay_file);
1061 EXPECT_NE(0, feof(delay_file)) << "Delay file not fully processed";
1062 read_count = fread(&temp_int8, sizeof(temp_int8), 1, drift_file);
1063 EXPECT_NE(0, feof(drift_file)) << "Drift file not fully processed";
1068 if (primary_count > 0) {
1069 int64_t exec_time = acc_ticks.Milliseconds();
1070 printf("\nTotal time: %.3f s, file time: %.2f s\n",
1071 exec_time * 0.001, primary_count * 0.01);
1072 printf("Time per frame: %.3f ms (average), %.3f ms (max),"
1074 (exec_time * 1.0) / primary_count,
1075 (max_time_us + max_time_reverse_us) / 1000.0,
1076 (min_time_us + min_time_reverse_us) / 1000.0);
1077 // Record the results with Perf test tools.
1078 webrtc::test::PrintResult("audioproc", "", "time_per_10ms_frame",
1079 (exec_time * 1000) / primary_count, "us", false);
1081 printf("Warning: no capture frames\n");
1087 } // namespace webrtc
1089 int main(int argc, char* argv[])
1091 webrtc::void_main(argc, argv);
1093 // Optional, but removes memory leak noise from Valgrind.
1094 google::protobuf::ShutdownProtobufLibrary();