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. 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 68 void PrintStat(const AudioProcessing::Statistic& stat) { 69 printf("%d, %d, %d\n", stat.average, 70 stat.maximum, 71 stat.minimum); 72 } 73 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. 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 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