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 raw and wav 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(" --no_reported_delay\n");
82 printf("\n -aecm Echo control mobile\n");
83 printf(" --aecm_echo_path_in_file FILE\n");
84 printf(" --aecm_echo_path_out_file FILE\n");
85 printf(" --no_comfort_noise\n");
86 printf(" --routing_mode MODE [0 - 4]\n");
87 printf("\n -agc Gain control\n");
88 printf(" --analog\n");
89 printf(" --adaptive_digital\n");
90 printf(" --fixed_digital\n");
91 printf(" --target_level LEVEL\n");
92 printf(" --compression_gain GAIN\n");
93 printf(" --limiter\n");
94 printf(" --no_limiter\n");
95 printf("\n -hpf High pass filter\n");
96 printf("\n -ns Noise suppression\n");
97 printf(" --ns_low\n");
98 printf(" --ns_moderate\n");
99 printf(" --ns_high\n");
100 printf(" --ns_very_high\n");
101 printf(" --ns_prob_file FILE\n");
102 printf("\n -vad Voice activity detection\n");
103 printf(" --vad_out_file FILE\n");
104 printf("\n -expns Experimental noise suppression\n");
105 printf("\n Level metrics (enabled by default)\n");
106 printf(" --no_level_metrics\n");
108 printf("Modifiers:\n");
109 printf(" --noasm Disable SSE optimization.\n");
110 printf(" --add_delay DELAY Add DELAY ms to input value.\n");
111 printf(" --delay DELAY Override input delay with DELAY ms.\n");
112 printf(" --perf Measure performance.\n");
113 printf(" --quiet Suppress text output.\n");
114 printf(" --no_progress Suppress progress.\n");
115 printf(" --raw_output Raw output instead of WAV file.\n");
116 printf(" --debug_file FILE Dump a debug recording.\n");
119 static float MicLevel2Gain(int level) {
120 return pow(10.0f, ((level - 127.0f) / 128.0f * 40.0f) / 20.0f);
123 static void SimulateMic(int mic_level, AudioFrame* frame) {
124 mic_level = std::min(std::max(mic_level, 0), 255);
125 float mic_gain = MicLevel2Gain(mic_level);
126 int num_samples = frame->samples_per_channel_ * frame->num_channels_;
128 for (int n = 0; n < num_samples; n++) {
129 v = floor(frame->data_[n] * mic_gain + 0.5);
130 v = std::max(std::min(32767.0f, v), -32768.0f);
131 frame->data_[n] = static_cast<int16_t>(v);
135 // void function for gtest.
136 void void_main(int argc, char* argv[]) {
137 if (argc > 1 && strcmp(argv[1], "--help") == 0) {
143 printf("Did you mean to run without arguments?\n");
144 printf("Try `process_test --help' for more information.\n\n");
147 scoped_ptr<AudioProcessing> apm(AudioProcessing::Create());
148 ASSERT_TRUE(apm.get() != NULL);
150 const char* pb_filename = NULL;
151 const char* far_filename = NULL;
152 const char* near_filename = NULL;
153 std::string out_filename;
154 const char* vad_out_filename = NULL;
155 const char* ns_prob_filename = NULL;
156 const char* aecm_echo_path_in_filename = NULL;
157 const char* aecm_echo_path_out_filename = NULL;
159 int32_t sample_rate_hz = 16000;
161 int num_capture_input_channels = 1;
162 int num_capture_output_channels = 1;
163 int num_render_channels = 1;
165 int samples_per_channel = sample_rate_hz / 100;
167 bool simulating = false;
168 bool perf_testing = false;
170 bool progress = true;
171 bool raw_output = false;
172 int extra_delay_ms = 0;
173 int override_delay_ms = 0;
175 ASSERT_EQ(apm->kNoError, apm->level_estimator()->Enable(true));
176 for (int i = 1; i < argc; i++) {
177 if (strcmp(argv[i], "-pb") == 0) {
179 ASSERT_LT(i, argc) << "Specify protobuf filename after -pb";
180 pb_filename = argv[i];
182 } else if (strcmp(argv[i], "-ir") == 0) {
184 ASSERT_LT(i, argc) << "Specify filename after -ir";
185 far_filename = argv[i];
188 } else if (strcmp(argv[i], "-i") == 0) {
190 ASSERT_LT(i, argc) << "Specify filename after -i";
191 near_filename = argv[i];
194 } else if (strcmp(argv[i], "-o") == 0) {
196 ASSERT_LT(i, argc) << "Specify filename without extension after -o";
197 out_filename = argv[i];
199 } else if (strcmp(argv[i], "-fs") == 0) {
201 ASSERT_LT(i, argc) << "Specify sample rate after -fs";
202 ASSERT_EQ(1, sscanf(argv[i], "%d", &sample_rate_hz));
203 samples_per_channel = sample_rate_hz / 100;
205 } else if (strcmp(argv[i], "-ch") == 0) {
207 ASSERT_LT(i + 1, argc) << "Specify number of channels after -ch";
208 ASSERT_EQ(1, sscanf(argv[i], "%d", &num_capture_input_channels));
210 ASSERT_EQ(1, sscanf(argv[i], "%d", &num_capture_output_channels));
212 } else if (strcmp(argv[i], "-rch") == 0) {
214 ASSERT_LT(i, argc) << "Specify number of channels after -rch";
215 ASSERT_EQ(1, sscanf(argv[i], "%d", &num_render_channels));
217 } else if (strcmp(argv[i], "-aec") == 0) {
218 ASSERT_EQ(apm->kNoError, apm->echo_cancellation()->Enable(true));
219 ASSERT_EQ(apm->kNoError,
220 apm->echo_cancellation()->enable_metrics(true));
221 ASSERT_EQ(apm->kNoError,
222 apm->echo_cancellation()->enable_delay_logging(true));
224 } else if (strcmp(argv[i], "--drift_compensation") == 0) {
225 ASSERT_EQ(apm->kNoError, apm->echo_cancellation()->Enable(true));
226 // TODO(ajm): this is enabled in the VQE test app by default. Investigate
227 // why it can give better performance despite passing zeros.
228 ASSERT_EQ(apm->kNoError,
229 apm->echo_cancellation()->enable_drift_compensation(true));
230 } else if (strcmp(argv[i], "--no_drift_compensation") == 0) {
231 ASSERT_EQ(apm->kNoError, apm->echo_cancellation()->Enable(true));
232 ASSERT_EQ(apm->kNoError,
233 apm->echo_cancellation()->enable_drift_compensation(false));
235 } else if (strcmp(argv[i], "--no_echo_metrics") == 0) {
236 ASSERT_EQ(apm->kNoError, apm->echo_cancellation()->Enable(true));
237 ASSERT_EQ(apm->kNoError,
238 apm->echo_cancellation()->enable_metrics(false));
240 } else if (strcmp(argv[i], "--no_delay_logging") == 0) {
241 ASSERT_EQ(apm->kNoError, apm->echo_cancellation()->Enable(true));
242 ASSERT_EQ(apm->kNoError,
243 apm->echo_cancellation()->enable_delay_logging(false));
245 } else if (strcmp(argv[i], "--no_level_metrics") == 0) {
246 ASSERT_EQ(apm->kNoError, apm->level_estimator()->Enable(false));
248 } else if (strcmp(argv[i], "--aec_suppression_level") == 0) {
250 ASSERT_LT(i, argc) << "Specify level after --aec_suppression_level";
251 int suppression_level;
252 ASSERT_EQ(1, sscanf(argv[i], "%d", &suppression_level));
253 ASSERT_EQ(apm->kNoError,
254 apm->echo_cancellation()->set_suppression_level(
255 static_cast<webrtc::EchoCancellation::SuppressionLevel>(
256 suppression_level)));
258 } else if (strcmp(argv[i], "--extended_filter") == 0) {
260 config.Set<DelayCorrection>(new DelayCorrection(true));
261 apm->SetExtraOptions(config);
263 } else if (strcmp(argv[i], "--no_reported_delay") == 0) {
265 config.Set<ReportedDelay>(new ReportedDelay(false));
266 apm->SetExtraOptions(config);
268 } else if (strcmp(argv[i], "-aecm") == 0) {
269 ASSERT_EQ(apm->kNoError, apm->echo_control_mobile()->Enable(true));
271 } else if (strcmp(argv[i], "--aecm_echo_path_in_file") == 0) {
273 ASSERT_LT(i, argc) << "Specify filename after --aecm_echo_path_in_file";
274 aecm_echo_path_in_filename = argv[i];
276 } else if (strcmp(argv[i], "--aecm_echo_path_out_file") == 0) {
278 ASSERT_LT(i, argc) << "Specify filename after --aecm_echo_path_out_file";
279 aecm_echo_path_out_filename = argv[i];
281 } else if (strcmp(argv[i], "--no_comfort_noise") == 0) {
282 ASSERT_EQ(apm->kNoError,
283 apm->echo_control_mobile()->enable_comfort_noise(false));
285 } else if (strcmp(argv[i], "--routing_mode") == 0) {
287 ASSERT_LT(i, argc) << "Specify mode after --routing_mode";
289 ASSERT_EQ(1, sscanf(argv[i], "%d", &routing_mode));
290 ASSERT_EQ(apm->kNoError,
291 apm->echo_control_mobile()->set_routing_mode(
292 static_cast<webrtc::EchoControlMobile::RoutingMode>(
295 } else if (strcmp(argv[i], "-agc") == 0) {
296 ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
298 } else if (strcmp(argv[i], "--analog") == 0) {
299 ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
300 ASSERT_EQ(apm->kNoError,
301 apm->gain_control()->set_mode(GainControl::kAdaptiveAnalog));
303 } else if (strcmp(argv[i], "--adaptive_digital") == 0) {
304 ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
305 ASSERT_EQ(apm->kNoError,
306 apm->gain_control()->set_mode(GainControl::kAdaptiveDigital));
308 } else if (strcmp(argv[i], "--fixed_digital") == 0) {
309 ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
310 ASSERT_EQ(apm->kNoError,
311 apm->gain_control()->set_mode(GainControl::kFixedDigital));
313 } else if (strcmp(argv[i], "--target_level") == 0) {
316 ASSERT_EQ(1, sscanf(argv[i], "%d", &level));
318 ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
319 ASSERT_EQ(apm->kNoError,
320 apm->gain_control()->set_target_level_dbfs(level));
322 } else if (strcmp(argv[i], "--compression_gain") == 0) {
325 ASSERT_EQ(1, sscanf(argv[i], "%d", &gain));
327 ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
328 ASSERT_EQ(apm->kNoError,
329 apm->gain_control()->set_compression_gain_db(gain));
331 } else if (strcmp(argv[i], "--limiter") == 0) {
332 ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
333 ASSERT_EQ(apm->kNoError,
334 apm->gain_control()->enable_limiter(true));
336 } else if (strcmp(argv[i], "--no_limiter") == 0) {
337 ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
338 ASSERT_EQ(apm->kNoError,
339 apm->gain_control()->enable_limiter(false));
341 } else if (strcmp(argv[i], "-hpf") == 0) {
342 ASSERT_EQ(apm->kNoError, apm->high_pass_filter()->Enable(true));
344 } else if (strcmp(argv[i], "-ns") == 0) {
345 ASSERT_EQ(apm->kNoError, apm->noise_suppression()->Enable(true));
347 } else if (strcmp(argv[i], "--ns_low") == 0) {
348 ASSERT_EQ(apm->kNoError, apm->noise_suppression()->Enable(true));
349 ASSERT_EQ(apm->kNoError,
350 apm->noise_suppression()->set_level(NoiseSuppression::kLow));
352 } else if (strcmp(argv[i], "--ns_moderate") == 0) {
353 ASSERT_EQ(apm->kNoError, apm->noise_suppression()->Enable(true));
354 ASSERT_EQ(apm->kNoError,
355 apm->noise_suppression()->set_level(NoiseSuppression::kModerate));
357 } else if (strcmp(argv[i], "--ns_high") == 0) {
358 ASSERT_EQ(apm->kNoError, apm->noise_suppression()->Enable(true));
359 ASSERT_EQ(apm->kNoError,
360 apm->noise_suppression()->set_level(NoiseSuppression::kHigh));
362 } else if (strcmp(argv[i], "--ns_very_high") == 0) {
363 ASSERT_EQ(apm->kNoError, apm->noise_suppression()->Enable(true));
364 ASSERT_EQ(apm->kNoError,
365 apm->noise_suppression()->set_level(NoiseSuppression::kVeryHigh));
367 } else if (strcmp(argv[i], "--ns_prob_file") == 0) {
369 ASSERT_LT(i, argc) << "Specify filename after --ns_prob_file";
370 ns_prob_filename = argv[i];
372 } else if (strcmp(argv[i], "-vad") == 0) {
373 ASSERT_EQ(apm->kNoError, apm->voice_detection()->Enable(true));
375 } else if (strcmp(argv[i], "--vad_very_low") == 0) {
376 ASSERT_EQ(apm->kNoError, apm->voice_detection()->Enable(true));
377 ASSERT_EQ(apm->kNoError,
378 apm->voice_detection()->set_likelihood(
379 VoiceDetection::kVeryLowLikelihood));
381 } else if (strcmp(argv[i], "--vad_low") == 0) {
382 ASSERT_EQ(apm->kNoError, apm->voice_detection()->Enable(true));
383 ASSERT_EQ(apm->kNoError,
384 apm->voice_detection()->set_likelihood(
385 VoiceDetection::kLowLikelihood));
387 } else if (strcmp(argv[i], "--vad_moderate") == 0) {
388 ASSERT_EQ(apm->kNoError, apm->voice_detection()->Enable(true));
389 ASSERT_EQ(apm->kNoError,
390 apm->voice_detection()->set_likelihood(
391 VoiceDetection::kModerateLikelihood));
393 } else if (strcmp(argv[i], "--vad_high") == 0) {
394 ASSERT_EQ(apm->kNoError, apm->voice_detection()->Enable(true));
395 ASSERT_EQ(apm->kNoError,
396 apm->voice_detection()->set_likelihood(
397 VoiceDetection::kHighLikelihood));
399 } else if (strcmp(argv[i], "--vad_out_file") == 0) {
401 ASSERT_LT(i, argc) << "Specify filename after --vad_out_file";
402 vad_out_filename = argv[i];
404 } else if (strcmp(argv[i], "-expns") == 0) {
406 config.Set<ExperimentalNs>(new ExperimentalNs(true));
407 apm->SetExtraOptions(config);
409 } else if (strcmp(argv[i], "--noasm") == 0) {
410 WebRtc_GetCPUInfo = WebRtc_GetCPUInfoNoASM;
411 // We need to reinitialize here if components have already been enabled.
412 ASSERT_EQ(apm->kNoError, apm->Initialize());
414 } else if (strcmp(argv[i], "--add_delay") == 0) {
416 ASSERT_EQ(1, sscanf(argv[i], "%d", &extra_delay_ms));
418 } else if (strcmp(argv[i], "--delay") == 0) {
420 ASSERT_EQ(1, sscanf(argv[i], "%d", &override_delay_ms));
422 } else if (strcmp(argv[i], "--perf") == 0) {
425 } else if (strcmp(argv[i], "--quiet") == 0) {
429 } else if (strcmp(argv[i], "--no_progress") == 0) {
432 } else if (strcmp(argv[i], "--raw_output") == 0) {
435 } else if (strcmp(argv[i], "--debug_file") == 0) {
437 ASSERT_LT(i, argc) << "Specify filename after --debug_file";
438 ASSERT_EQ(apm->kNoError, apm->StartDebugRecording(argv[i]));
440 FAIL() << "Unrecognized argument " << argv[i];
443 // If we're reading a protobuf file, ensure a simulation hasn't also
444 // been requested (which makes no sense...)
445 ASSERT_FALSE(pb_filename && simulating);
448 printf("Sample rate: %d Hz\n", sample_rate_hz);
449 printf("Primary channels: %d (in), %d (out)\n",
450 num_capture_input_channels,
451 num_capture_output_channels);
452 printf("Reverse channels: %d \n", num_render_channels);
455 const std::string out_path = webrtc::test::OutputPath();
456 const char far_file_default[] = "apm_far.pcm";
457 const char near_file_default[] = "apm_near.pcm";
458 const char event_filename[] = "apm_event.dat";
459 const char delay_filename[] = "apm_delay.dat";
460 const char drift_filename[] = "apm_drift.dat";
461 const std::string vad_file_default = out_path + "vad_out.dat";
462 const std::string ns_prob_file_default = out_path + "ns_prob.dat";
465 far_filename = far_file_default;
466 near_filename = near_file_default;
469 if (out_filename.size() == 0) {
470 out_filename = out_path + "out";
473 if (!vad_out_filename) {
474 vad_out_filename = vad_file_default.c_str();
477 if (!ns_prob_filename) {
478 ns_prob_filename = ns_prob_file_default.c_str();
481 FILE* pb_file = NULL;
482 FILE* far_file = NULL;
483 FILE* near_file = NULL;
484 FILE* event_file = NULL;
485 FILE* delay_file = NULL;
486 FILE* drift_file = NULL;
487 FILE* vad_out_file = NULL;
488 FILE* ns_prob_file = NULL;
489 FILE* aecm_echo_path_in_file = NULL;
490 FILE* aecm_echo_path_out_file = NULL;
492 scoped_ptr<WavFile> output_wav_file;
493 scoped_ptr<RawFile> output_raw_file;
496 pb_file = OpenFile(pb_filename, "rb");
499 far_file = OpenFile(far_filename, "rb");
502 near_file = OpenFile(near_filename, "rb");
504 event_file = OpenFile(event_filename, "rb");
505 delay_file = OpenFile(delay_filename, "rb");
506 drift_file = OpenFile(drift_filename, "rb");
510 int near_size_bytes = 0;
513 stat(pb_filename, &st);
514 // Crude estimate, but should be good enough.
515 near_size_bytes = st.st_size / 3;
518 stat(near_filename, &st);
519 near_size_bytes = st.st_size;
522 if (apm->voice_detection()->is_enabled()) {
523 vad_out_file = OpenFile(vad_out_filename, "wb");
526 if (apm->noise_suppression()->is_enabled()) {
527 ns_prob_file = OpenFile(ns_prob_filename, "wb");
530 if (aecm_echo_path_in_filename != NULL) {
531 aecm_echo_path_in_file = OpenFile(aecm_echo_path_in_filename, "rb");
533 const size_t path_size =
534 apm->echo_control_mobile()->echo_path_size_bytes();
535 scoped_ptr<char[]> echo_path(new char[path_size]);
536 ASSERT_EQ(path_size, fread(echo_path.get(),
539 aecm_echo_path_in_file));
540 EXPECT_EQ(apm->kNoError,
541 apm->echo_control_mobile()->SetEchoPath(echo_path.get(),
543 fclose(aecm_echo_path_in_file);
544 aecm_echo_path_in_file = NULL;
547 if (aecm_echo_path_out_filename != NULL) {
548 aecm_echo_path_out_file = OpenFile(aecm_echo_path_out_filename, "wb");
551 size_t read_count = 0;
552 int reverse_count = 0;
553 int primary_count = 0;
554 int near_read_bytes = 0;
555 TickInterval acc_ticks;
557 AudioFrame far_frame;
558 AudioFrame near_frame;
561 int drift_samples = 0;
562 int capture_level = 127;
563 int8_t stream_has_voice = 0;
564 float ns_speech_prob = 0.0f;
566 TickTime t0 = TickTime::Now();
568 int64_t max_time_us = 0;
569 int64_t max_time_reverse_us = 0;
570 int64_t min_time_us = 1e6;
571 int64_t min_time_reverse_us = 1e6;
573 // TODO(ajm): Ideally we would refactor this block into separate functions,
574 // but for now we want to share the variables.
577 scoped_ptr<ChannelBuffer<float> > reverse_cb;
578 scoped_ptr<ChannelBuffer<float> > primary_cb;
579 int output_sample_rate = 32000;
580 AudioProcessing::ChannelLayout output_layout = AudioProcessing::kMono;
581 while (ReadMessageFromFile(pb_file, &event_msg)) {
582 std::ostringstream trace_stream;
583 trace_stream << "Processed frames: " << reverse_count << " (reverse), "
584 << primary_count << " (primary)";
585 SCOPED_TRACE(trace_stream.str());
587 if (event_msg.type() == Event::INIT) {
588 ASSERT_TRUE(event_msg.has_init());
589 const Init msg = event_msg.init();
591 ASSERT_TRUE(msg.has_sample_rate());
592 ASSERT_TRUE(msg.has_num_input_channels());
593 ASSERT_TRUE(msg.has_num_output_channels());
594 ASSERT_TRUE(msg.has_num_reverse_channels());
595 int reverse_sample_rate = msg.sample_rate();
596 if (msg.has_reverse_sample_rate()) {
597 reverse_sample_rate = msg.reverse_sample_rate();
599 output_sample_rate = msg.sample_rate();
600 if (msg.has_output_sample_rate()) {
601 output_sample_rate = msg.output_sample_rate();
603 output_layout = LayoutFromChannels(msg.num_output_channels());
604 ASSERT_EQ(kNoErr, apm->Initialize(
608 LayoutFromChannels(msg.num_input_channels()),
610 LayoutFromChannels(msg.num_reverse_channels())));
612 samples_per_channel = msg.sample_rate() / 100;
613 far_frame.sample_rate_hz_ = msg.sample_rate();
614 far_frame.samples_per_channel_ = reverse_sample_rate / 100;
615 far_frame.num_channels_ = msg.num_reverse_channels();
616 near_frame.sample_rate_hz_ = msg.sample_rate();
617 near_frame.samples_per_channel_ = samples_per_channel;
618 near_frame.num_channels_ = msg.num_input_channels();
619 reverse_cb.reset(new ChannelBuffer<float>(
620 far_frame.samples_per_channel_,
621 msg.num_reverse_channels()));
622 primary_cb.reset(new ChannelBuffer<float>(samples_per_channel,
623 msg.num_input_channels()));
626 printf("Init at frame: %d (primary), %d (reverse)\n",
627 primary_count, reverse_count);
628 printf(" Primary rates: %d Hz (in), %d Hz (out)\n",
629 msg.sample_rate(), output_sample_rate);
630 printf(" Primary channels: %d (in), %d (out)\n",
631 msg.num_input_channels(),
632 msg.num_output_channels());
633 printf(" Reverse rate: %d\n", reverse_sample_rate);
634 printf(" Reverse channels: %d\n", msg.num_reverse_channels());
638 // The WAV file needs to be reset every time, because it cant change
639 // it's sample rate or number of channels.
640 output_wav_file.reset(new WavFile(out_filename + ".wav",
642 msg.num_output_channels()));
645 } else if (event_msg.type() == Event::REVERSE_STREAM) {
646 ASSERT_TRUE(event_msg.has_reverse_stream());
647 ReverseStream msg = event_msg.reverse_stream();
650 ASSERT_TRUE(msg.has_data() ^ (msg.channel_size() > 0));
651 if (msg.has_data()) {
652 ASSERT_EQ(sizeof(int16_t) * far_frame.samples_per_channel_ *
653 far_frame.num_channels_, msg.data().size());
654 memcpy(far_frame.data_, msg.data().data(), msg.data().size());
656 for (int i = 0; i < msg.channel_size(); ++i) {
657 reverse_cb->CopyFrom(msg.channel(i).data(), i);
662 t0 = TickTime::Now();
665 if (msg.has_data()) {
666 ASSERT_EQ(apm->kNoError,
667 apm->AnalyzeReverseStream(&far_frame));
669 ASSERT_EQ(apm->kNoError,
670 apm->AnalyzeReverseStream(
671 reverse_cb->channels(),
672 far_frame.samples_per_channel_,
673 far_frame.sample_rate_hz_,
674 LayoutFromChannels(far_frame.num_channels_)));
678 t1 = TickTime::Now();
679 TickInterval tick_diff = t1 - t0;
680 acc_ticks += tick_diff;
681 if (tick_diff.Microseconds() > max_time_reverse_us) {
682 max_time_reverse_us = tick_diff.Microseconds();
684 if (tick_diff.Microseconds() < min_time_reverse_us) {
685 min_time_reverse_us = tick_diff.Microseconds();
689 } else if (event_msg.type() == Event::STREAM) {
690 ASSERT_TRUE(event_msg.has_stream());
691 const Stream msg = event_msg.stream();
694 // ProcessStream could have changed this for the output frame.
695 near_frame.num_channels_ = apm->num_input_channels();
697 ASSERT_TRUE(msg.has_input_data() ^ (msg.input_channel_size() > 0));
698 if (msg.has_input_data()) {
699 ASSERT_EQ(sizeof(int16_t) * samples_per_channel *
700 near_frame.num_channels_, msg.input_data().size());
701 memcpy(near_frame.data_,
702 msg.input_data().data(),
703 msg.input_data().size());
704 near_read_bytes += msg.input_data().size();
706 for (int i = 0; i < msg.input_channel_size(); ++i) {
707 primary_cb->CopyFrom(msg.input_channel(i).data(), i);
708 near_read_bytes += msg.input_channel(i).size();
712 if (progress && primary_count % 100 == 0) {
713 near_read_bytes = std::min(near_read_bytes, near_size_bytes);
714 printf("%.0f%% complete\r",
715 (near_read_bytes * 100.0) / near_size_bytes);
720 t0 = TickTime::Now();
723 ASSERT_EQ(apm->kNoError,
724 apm->gain_control()->set_stream_analog_level(msg.level()));
725 delay_ms = msg.delay() + extra_delay_ms;
726 if (override_delay_ms) {
727 delay_ms = override_delay_ms;
729 ASSERT_EQ(apm->kNoError,
730 apm->set_stream_delay_ms(delay_ms));
731 apm->echo_cancellation()->set_stream_drift_samples(msg.drift());
733 if (msg.has_keypress()) {
734 apm->set_stream_key_pressed(msg.keypress());
736 apm->set_stream_key_pressed(true);
739 int err = apm->kNoError;
740 if (msg.has_input_data()) {
741 err = apm->ProcessStream(&near_frame);
742 ASSERT_TRUE(near_frame.num_channels_ == apm->num_output_channels());
744 err = apm->ProcessStream(
745 primary_cb->channels(),
746 near_frame.samples_per_channel_,
747 near_frame.sample_rate_hz_,
748 LayoutFromChannels(near_frame.num_channels_),
751 primary_cb->channels());
754 if (err == apm->kBadStreamParameterWarning) {
755 printf("Bad parameter warning. %s\n", trace_stream.str().c_str());
757 ASSERT_TRUE(err == apm->kNoError ||
758 err == apm->kBadStreamParameterWarning);
761 static_cast<int8_t>(apm->voice_detection()->stream_has_voice());
762 if (vad_out_file != NULL) {
763 ASSERT_EQ(1u, fwrite(&stream_has_voice,
764 sizeof(stream_has_voice),
769 if (ns_prob_file != NULL) {
770 ns_speech_prob = apm->noise_suppression()->speech_probability();
771 ASSERT_EQ(1u, fwrite(&ns_speech_prob,
772 sizeof(ns_speech_prob),
778 t1 = TickTime::Now();
779 TickInterval tick_diff = t1 - t0;
780 acc_ticks += tick_diff;
781 if (tick_diff.Microseconds() > max_time_us) {
782 max_time_us = tick_diff.Microseconds();
784 if (tick_diff.Microseconds() < min_time_us) {
785 min_time_us = tick_diff.Microseconds();
789 const size_t samples_per_channel = output_sample_rate / 100;
790 if (msg.has_input_data()) {
791 if (raw_output && !output_raw_file) {
792 output_raw_file.reset(new RawFile(out_filename + ".pcm"));
794 WriteIntData(near_frame.data_,
795 apm->num_output_channels() * samples_per_channel,
796 output_wav_file.get(),
797 output_raw_file.get());
799 if (raw_output && !output_raw_file) {
800 output_raw_file.reset(new RawFile(out_filename + ".float"));
802 WriteFloatData(primary_cb->channels(),
804 apm->num_output_channels(),
805 output_wav_file.get(),
806 output_raw_file.get());
811 ASSERT_TRUE(feof(pb_file));
818 kResetEventDeprecated
821 while (simulating || feof(event_file) == 0) {
822 std::ostringstream trace_stream;
823 trace_stream << "Processed frames: " << reverse_count << " (reverse), "
824 << primary_count << " (primary)";
825 SCOPED_TRACE(trace_stream.str());
828 if (far_file == NULL) {
829 event = kCaptureEvent;
831 if (event == kRenderEvent) {
832 event = kCaptureEvent;
834 event = kRenderEvent;
838 read_count = fread(&event, sizeof(event), 1, event_file);
839 if (read_count != 1) {
844 far_frame.sample_rate_hz_ = sample_rate_hz;
845 far_frame.samples_per_channel_ = samples_per_channel;
846 far_frame.num_channels_ = num_render_channels;
847 near_frame.sample_rate_hz_ = sample_rate_hz;
848 near_frame.samples_per_channel_ = samples_per_channel;
850 if (event == kInitializeEvent || event == kResetEventDeprecated) {
852 fread(&sample_rate_hz, sizeof(sample_rate_hz), 1, event_file));
853 samples_per_channel = sample_rate_hz / 100;
855 int32_t unused_device_sample_rate_hz;
857 fread(&unused_device_sample_rate_hz,
858 sizeof(unused_device_sample_rate_hz),
862 ASSERT_EQ(kNoErr, apm->Initialize(
866 LayoutFromChannels(num_capture_input_channels),
867 LayoutFromChannels(num_capture_output_channels),
868 LayoutFromChannels(num_render_channels)));
870 far_frame.sample_rate_hz_ = sample_rate_hz;
871 far_frame.samples_per_channel_ = samples_per_channel;
872 far_frame.num_channels_ = num_render_channels;
873 near_frame.sample_rate_hz_ = sample_rate_hz;
874 near_frame.samples_per_channel_ = samples_per_channel;
877 // The WAV file needs to be reset every time, because it can't change
878 // it's sample rate or number of channels.
879 output_wav_file.reset(new WavFile(out_filename + ".wav",
881 num_capture_output_channels));
885 printf("Init at frame: %d (primary), %d (reverse)\n",
886 primary_count, reverse_count);
887 printf(" Sample rate: %d Hz\n", sample_rate_hz);
890 } else if (event == kRenderEvent) {
893 size_t size = samples_per_channel * num_render_channels;
894 read_count = fread(far_frame.data_,
900 if (read_count != size) {
901 // Read an equal amount from the near file to avoid errors due to
902 // not reaching end-of-file.
903 EXPECT_EQ(0, fseek(near_file, read_count * sizeof(int16_t),
905 break; // This is expected.
908 ASSERT_EQ(size, read_count);
912 t0 = TickTime::Now();
915 ASSERT_EQ(apm->kNoError,
916 apm->AnalyzeReverseStream(&far_frame));
919 t1 = TickTime::Now();
920 TickInterval tick_diff = t1 - t0;
921 acc_ticks += tick_diff;
922 if (tick_diff.Microseconds() > max_time_reverse_us) {
923 max_time_reverse_us = tick_diff.Microseconds();
925 if (tick_diff.Microseconds() < min_time_reverse_us) {
926 min_time_reverse_us = tick_diff.Microseconds();
930 } else if (event == kCaptureEvent) {
932 near_frame.num_channels_ = num_capture_input_channels;
934 size_t size = samples_per_channel * num_capture_input_channels;
935 read_count = fread(near_frame.data_,
940 near_read_bytes += read_count * sizeof(int16_t);
941 if (progress && primary_count % 100 == 0) {
942 printf("%.0f%% complete\r",
943 (near_read_bytes * 100.0) / near_size_bytes);
947 if (read_count != size) {
948 break; // This is expected.
954 ASSERT_EQ(size, read_count);
956 // TODO(ajm): sizeof(delay_ms) for current files?
958 fread(&delay_ms, 2, 1, delay_file));
960 fread(&drift_samples, sizeof(drift_samples), 1, drift_file));
963 if (apm->gain_control()->is_enabled() &&
964 apm->gain_control()->mode() == GainControl::kAdaptiveAnalog) {
965 SimulateMic(capture_level, &near_frame);
969 t0 = TickTime::Now();
972 const int capture_level_in = capture_level;
973 ASSERT_EQ(apm->kNoError,
974 apm->gain_control()->set_stream_analog_level(capture_level));
975 delay_ms += extra_delay_ms;
976 if (override_delay_ms) {
977 delay_ms = override_delay_ms;
979 ASSERT_EQ(apm->kNoError,
980 apm->set_stream_delay_ms(delay_ms));
981 apm->echo_cancellation()->set_stream_drift_samples(drift_samples);
983 apm->set_stream_key_pressed(true);
985 int err = apm->ProcessStream(&near_frame);
986 if (err == apm->kBadStreamParameterWarning) {
987 printf("Bad parameter warning. %s\n", trace_stream.str().c_str());
989 ASSERT_TRUE(err == apm->kNoError ||
990 err == apm->kBadStreamParameterWarning);
991 ASSERT_TRUE(near_frame.num_channels_ == apm->num_output_channels());
993 capture_level = apm->gain_control()->stream_analog_level();
996 static_cast<int8_t>(apm->voice_detection()->stream_has_voice());
997 if (vad_out_file != NULL) {
998 ASSERT_EQ(1u, fwrite(&stream_has_voice,
999 sizeof(stream_has_voice),
1004 if (ns_prob_file != NULL) {
1005 ns_speech_prob = apm->noise_suppression()->speech_probability();
1006 ASSERT_EQ(1u, fwrite(&ns_speech_prob,
1007 sizeof(ns_speech_prob),
1012 if (apm->gain_control()->mode() != GainControl::kAdaptiveAnalog) {
1013 ASSERT_EQ(capture_level_in, capture_level);
1017 t1 = TickTime::Now();
1018 TickInterval tick_diff = t1 - t0;
1019 acc_ticks += tick_diff;
1020 if (tick_diff.Microseconds() > max_time_us) {
1021 max_time_us = tick_diff.Microseconds();
1023 if (tick_diff.Microseconds() < min_time_us) {
1024 min_time_us = tick_diff.Microseconds();
1028 if (raw_output && !output_raw_file) {
1029 output_raw_file.reset(new RawFile(out_filename + ".pcm"));
1031 if (!raw_output && !output_wav_file) {
1032 output_wav_file.reset(new WavFile(out_filename + ".wav",
1034 num_capture_output_channels));
1036 WriteIntData(near_frame.data_,
1038 output_wav_file.get(),
1039 output_raw_file.get());
1042 FAIL() << "Event " << event << " is unrecognized";
1046 printf("100%% complete\r");
1048 if (aecm_echo_path_out_file != NULL) {
1049 const size_t path_size =
1050 apm->echo_control_mobile()->echo_path_size_bytes();
1051 scoped_ptr<char[]> echo_path(new char[path_size]);
1052 apm->echo_control_mobile()->GetEchoPath(echo_path.get(), path_size);
1053 ASSERT_EQ(path_size, fwrite(echo_path.get(),
1056 aecm_echo_path_out_file));
1057 fclose(aecm_echo_path_out_file);
1058 aecm_echo_path_out_file = NULL;
1062 printf("\nProcessed frames: %d (primary), %d (reverse)\n",
1063 primary_count, reverse_count);
1065 if (apm->level_estimator()->is_enabled()) {
1066 printf("\n--Level metrics--\n");
1067 printf("RMS: %d dBFS\n", -apm->level_estimator()->RMS());
1069 if (apm->echo_cancellation()->are_metrics_enabled()) {
1070 EchoCancellation::Metrics metrics;
1071 apm->echo_cancellation()->GetMetrics(&metrics);
1072 printf("\n--Echo metrics--\n");
1073 printf("(avg, max, min)\n");
1075 PrintStat(metrics.echo_return_loss);
1077 PrintStat(metrics.echo_return_loss_enhancement);
1079 PrintStat(metrics.a_nlp);
1081 if (apm->echo_cancellation()->is_delay_logging_enabled()) {
1084 apm->echo_cancellation()->GetDelayMetrics(&median, &std);
1085 printf("\n--Delay metrics--\n");
1086 printf("Median: %3d\n", median);
1087 printf("Standard deviation: %3d\n", std);
1094 read_count = fread(&temp_int8, sizeof(temp_int8), 1, far_file);
1095 EXPECT_NE(0, feof(far_file)) << "Far-end file not fully processed";
1098 read_count = fread(&temp_int8, sizeof(temp_int8), 1, near_file);
1099 EXPECT_NE(0, feof(near_file)) << "Near-end file not fully processed";
1102 read_count = fread(&temp_int8, sizeof(temp_int8), 1, event_file);
1103 EXPECT_NE(0, feof(event_file)) << "Event file not fully processed";
1104 read_count = fread(&temp_int8, sizeof(temp_int8), 1, delay_file);
1105 EXPECT_NE(0, feof(delay_file)) << "Delay file not fully processed";
1106 read_count = fread(&temp_int8, sizeof(temp_int8), 1, drift_file);
1107 EXPECT_NE(0, feof(drift_file)) << "Drift file not fully processed";
1112 if (primary_count > 0) {
1113 int64_t exec_time = acc_ticks.Milliseconds();
1114 printf("\nTotal time: %.3f s, file time: %.2f s\n",
1115 exec_time * 0.001, primary_count * 0.01);
1116 printf("Time per frame: %.3f ms (average), %.3f ms (max),"
1118 (exec_time * 1.0) / primary_count,
1119 (max_time_us + max_time_reverse_us) / 1000.0,
1120 (min_time_us + min_time_reverse_us) / 1000.0);
1121 // Record the results with Perf test tools.
1122 webrtc::test::PrintResult("audioproc", "", "time_per_10ms_frame",
1123 (exec_time * 1000) / primary_count, "us", false);
1125 printf("Warning: no capture frames\n");
1131 } // namespace webrtc
1133 int main(int argc, char* argv[])
1135 webrtc::void_main(argc, argv);
1137 // Optional, but removes memory leak noise from Valgrind.
1138 google::protobuf::ShutdownProtobufLibrary();