629 lines
20 KiB
C++

/*
* Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include <stdio.h>
#include <string.h>
#ifdef WEBRTC_ANDROID
#include <sys/stat.h>
#endif
#include "tick_util.h"
#include "gtest/gtest.h"
#include "module_common_types.h"
#include "audio_processing.h"
#include "cpu_features_wrapper.h"
using webrtc::AudioFrame;
using webrtc::TickInterval;
using webrtc::TickTime;
using webrtc::AudioProcessing;
using webrtc::GainControl;
using webrtc::NoiseSuppression;
void usage() {
printf(
"Usage: process_test [options] [-ir REVERSE_FILE] [-i PRIMARY_FILE]\n");
printf(
" [-o OUT_FILE]\n");
printf(
"process_test is a test application for AudioProcessing.\n\n"
"When -ir or -i is specified the files will be processed directly in a\n"
"simulation mode. Otherwise the full set of test files is expected to be\n"
"present in the working directory.\n");
printf("\n");
printf("Options\n");
printf("General configuration:\n");
printf(" -fs SAMPLE_RATE_HZ\n");
printf(" -ch CHANNELS_IN CHANNELS_OUT\n");
printf(" -rch REVERSE_CHANNELS\n");
printf("\n");
printf("Component configuration:\n");
printf(
"All components are disabled by default. Each block below begins with a\n"
"flag to enable the component with default settings. The subsequent flags\n"
"in the block are used to provide configuration settings.\n");
printf("\n -aec Echo cancellation\n");
printf(" --drift_compensation\n");
printf(" --no_drift_compensation\n");
printf("\n -aecm Echo control mobile\n");
printf("\n -agc Gain control\n");
printf(" --analog\n");
printf(" --adaptive_digital\n");
printf(" --fixed_digital\n");
printf(" --target_level LEVEL\n");
printf(" --compression_gain GAIN\n");
printf(" --limiter\n");
printf(" --no_limiter\n");
printf("\n -hpf High pass filter\n");
printf("\n -ns Noise suppression\n");
printf(" --ns_low\n");
printf(" --ns_moderate\n");
printf(" --ns_high\n");
printf(" --ns_very_high\n");
printf("\n -vad Voice activity detection\n");
printf(" --vad_out_file FILE");
printf("\n");
printf("Modifiers:\n");
printf(" --perf Measure performance.\n");
printf(" --quiet Suppress text output.\n");
printf(" --no_progress Suppress progress.\n");
printf(" --version Print version information and exit.\n");
}
// void function for gtest.
void void_main(int argc, char* argv[]) {
if (argc > 1 && strcmp(argv[1], "--help") == 0) {
usage();
return;
}
if (argc < 2) {
printf("Did you mean to run without arguments?\n");
printf("Try `process_test --help' for more information.\n\n");
}
AudioProcessing* apm = AudioProcessing::Create(0);
ASSERT_TRUE(apm != NULL);
WebRtc_Word8 version[1024];
WebRtc_UWord32 version_bytes_remaining = sizeof(version);
WebRtc_UWord32 version_position = 0;
const char* far_filename = NULL;
const char* near_filename = NULL;
const char* out_filename = NULL;
const char* vad_out_filename = NULL;
int32_t sample_rate_hz = 16000;
int32_t device_sample_rate_hz = 16000;
int num_capture_input_channels = 1;
int num_capture_output_channels = 1;
int num_render_channels = 1;
int samples_per_channel = sample_rate_hz / 100;
bool simulating = false;
bool perf_testing = false;
bool verbose = true;
bool progress = true;
//bool interleaved = true;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-ir") == 0) {
i++;
ASSERT_LT(i, argc) << "Specify filename after -ir";
far_filename = argv[i];
simulating = true;
} else if (strcmp(argv[i], "-i") == 0) {
i++;
ASSERT_LT(i, argc) << "Specify filename after -i";
near_filename = argv[i];
simulating = true;
} else if (strcmp(argv[i], "-o") == 0) {
i++;
ASSERT_LT(i, argc) << "Specify filename after -o";
out_filename = argv[i];
} else if (strcmp(argv[i], "-fs") == 0) {
i++;
ASSERT_LT(i, argc) << "Specify sample rate after -fs";
ASSERT_EQ(1, sscanf(argv[i], "%d", &sample_rate_hz));
samples_per_channel = sample_rate_hz / 100;
ASSERT_EQ(apm->kNoError,
apm->set_sample_rate_hz(sample_rate_hz));
} else if (strcmp(argv[i], "-ch") == 0) {
i++;
ASSERT_LT(i + 1, argc) << "Specify number of channels after -ch";
ASSERT_EQ(1, sscanf(argv[i], "%d", &num_capture_input_channels));
i++;
ASSERT_EQ(1, sscanf(argv[i], "%d", &num_capture_output_channels));
ASSERT_EQ(apm->kNoError,
apm->set_num_channels(num_capture_input_channels,
num_capture_output_channels));
} else if (strcmp(argv[i], "-rch") == 0) {
i++;
ASSERT_LT(i, argc) << "Specify number of channels after -rch";
ASSERT_EQ(1, sscanf(argv[i], "%d", &num_render_channels));
ASSERT_EQ(apm->kNoError,
apm->set_num_reverse_channels(num_render_channels));
} else if (strcmp(argv[i], "-aec") == 0) {
ASSERT_EQ(apm->kNoError, apm->echo_cancellation()->Enable(true));
} else if (strcmp(argv[i], "-noasm") == 0) {
WebRtc_GetCPUInfo = WebRtc_GetCPUInfoNoASM;
} else if (strcmp(argv[i], "--drift_compensation") == 0) {
ASSERT_EQ(apm->kNoError, apm->echo_cancellation()->Enable(true));
// TODO(ajm): this is enabled in the VQE test app by default. Investigate
// why it can give better performance despite passing zeros.
ASSERT_EQ(apm->kNoError,
apm->echo_cancellation()->enable_drift_compensation(true));
} else if (strcmp(argv[i], "--no_drift_compensation") == 0) {
ASSERT_EQ(apm->kNoError, apm->echo_cancellation()->Enable(true));
ASSERT_EQ(apm->kNoError,
apm->echo_cancellation()->enable_drift_compensation(false));
} else if (strcmp(argv[i], "-aecm") == 0) {
ASSERT_EQ(apm->kNoError, apm->echo_control_mobile()->Enable(true));
} else if (strcmp(argv[i], "-agc") == 0) {
ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
} else if (strcmp(argv[i], "--analog") == 0) {
ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
ASSERT_EQ(apm->kNoError,
apm->gain_control()->set_mode(GainControl::kAdaptiveAnalog));
} else if (strcmp(argv[i], "--adaptive_digital") == 0) {
ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
ASSERT_EQ(apm->kNoError,
apm->gain_control()->set_mode(GainControl::kAdaptiveDigital));
} else if (strcmp(argv[i], "--fixed_digital") == 0) {
ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
ASSERT_EQ(apm->kNoError,
apm->gain_control()->set_mode(GainControl::kFixedDigital));
} else if (strcmp(argv[i], "--target_level") == 0) {
i++;
int level;
ASSERT_EQ(1, sscanf(argv[i], "%d", &level));
ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
ASSERT_EQ(apm->kNoError,
apm->gain_control()->set_target_level_dbfs(level));
} else if (strcmp(argv[i], "--compression_gain") == 0) {
i++;
int gain;
ASSERT_EQ(1, sscanf(argv[i], "%d", &gain));
ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
ASSERT_EQ(apm->kNoError,
apm->gain_control()->set_compression_gain_db(gain));
} else if (strcmp(argv[i], "--limiter") == 0) {
ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
ASSERT_EQ(apm->kNoError,
apm->gain_control()->enable_limiter(true));
} else if (strcmp(argv[i], "--no_limiter") == 0) {
ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true));
ASSERT_EQ(apm->kNoError,
apm->gain_control()->enable_limiter(false));
} else if (strcmp(argv[i], "-hpf") == 0) {
ASSERT_EQ(apm->kNoError, apm->high_pass_filter()->Enable(true));
} else if (strcmp(argv[i], "-ns") == 0) {
ASSERT_EQ(apm->kNoError, apm->noise_suppression()->Enable(true));
} else if (strcmp(argv[i], "--ns_low") == 0) {
ASSERT_EQ(apm->kNoError, apm->noise_suppression()->Enable(true));
ASSERT_EQ(apm->kNoError,
apm->noise_suppression()->set_level(NoiseSuppression::kLow));
} else if (strcmp(argv[i], "--ns_moderate") == 0) {
ASSERT_EQ(apm->kNoError, apm->noise_suppression()->Enable(true));
ASSERT_EQ(apm->kNoError,
apm->noise_suppression()->set_level(NoiseSuppression::kModerate));
} else if (strcmp(argv[i], "--ns_high") == 0) {
ASSERT_EQ(apm->kNoError, apm->noise_suppression()->Enable(true));
ASSERT_EQ(apm->kNoError,
apm->noise_suppression()->set_level(NoiseSuppression::kHigh));
} else if (strcmp(argv[i], "--ns_very_high") == 0) {
ASSERT_EQ(apm->kNoError, apm->noise_suppression()->Enable(true));
ASSERT_EQ(apm->kNoError,
apm->noise_suppression()->set_level(NoiseSuppression::kVeryHigh));
} else if (strcmp(argv[i], "-vad") == 0) {
ASSERT_EQ(apm->kNoError, apm->voice_detection()->Enable(true));
} else if (strcmp(argv[i], "--vad_out_file") == 0) {
i++;
ASSERT_LT(i, argc) << "Specify filename after --vad_out_file";
vad_out_filename = argv[i];
} else if (strcmp(argv[i], "--perf") == 0) {
perf_testing = true;
} else if (strcmp(argv[i], "--quiet") == 0) {
verbose = false;
progress = false;
} else if (strcmp(argv[i], "--no_progress") == 0) {
progress = false;
} else if (strcmp(argv[i], "--version") == 0) {
ASSERT_EQ(apm->kNoError, apm->Version(version,
version_bytes_remaining,
version_position));
printf("%s\n", version);
return;
} else {
FAIL() << "Unrecognized argument " << argv[i];
}
}
if (verbose) {
printf("Sample rate: %d Hz\n", sample_rate_hz);
printf("Primary channels: %d (in), %d (out)\n",
num_capture_input_channels,
num_capture_output_channels);
printf("Reverse channels: %d \n", num_render_channels);
}
const char far_file_default[] = "apm_far.pcm";
const char near_file_default[] = "apm_near.pcm";
const char out_file_default[] = "out.pcm";
const char event_filename[] = "apm_event.dat";
const char delay_filename[] = "apm_delay.dat";
const char drift_filename[] = "apm_drift.dat";
const char vad_file_default[] = "vad_out.dat";
if (!simulating) {
far_filename = far_file_default;
near_filename = near_file_default;
}
if (out_filename == NULL) {
out_filename = out_file_default;
}
if (vad_out_filename == NULL) {
vad_out_filename = vad_file_default;
}
FILE* far_file = NULL;
FILE* near_file = NULL;
FILE* out_file = NULL;
FILE* event_file = NULL;
FILE* delay_file = NULL;
FILE* drift_file = NULL;
FILE* vad_out_file = NULL;
if (far_filename != NULL) {
far_file = fopen(far_filename, "rb");
ASSERT_TRUE(NULL != far_file) << "Unable to open far-end audio file "
<< far_filename;
}
near_file = fopen(near_filename, "rb");
ASSERT_TRUE(NULL != near_file) << "Unable to open near-end audio file "
<< near_filename;
struct stat st;
stat(near_filename, &st);
int near_size_samples = st.st_size / sizeof(int16_t);
out_file = fopen(out_filename, "wb");
ASSERT_TRUE(NULL != out_file) << "Unable to open output audio file "
<< out_filename;
if (!simulating) {
event_file = fopen(event_filename, "rb");
ASSERT_TRUE(NULL != event_file) << "Unable to open event file "
<< event_filename;
delay_file = fopen(delay_filename, "rb");
ASSERT_TRUE(NULL != delay_file) << "Unable to open buffer file "
<< delay_filename;
drift_file = fopen(drift_filename, "rb");
ASSERT_TRUE(NULL != drift_file) << "Unable to open drift file "
<< drift_filename;
}
if (apm->voice_detection()->is_enabled()) {
vad_out_file = fopen(vad_out_filename, "wb");
ASSERT_TRUE(NULL != vad_out_file) << "Unable to open VAD output file "
<< vad_out_file;
}
enum Events {
kInitializeEvent,
kRenderEvent,
kCaptureEvent,
kResetEventDeprecated
};
int16_t event = 0;
size_t read_count = 0;
int reverse_count = 0;
int primary_count = 0;
int near_read_samples = 0;
TickInterval acc_ticks;
AudioFrame far_frame;
far_frame._frequencyInHz = sample_rate_hz;
AudioFrame near_frame;
near_frame._frequencyInHz = sample_rate_hz;
int delay_ms = 0;
int drift_samples = 0;
int capture_level = 127;
int8_t stream_has_voice = 0;
TickTime t0 = TickTime::Now();
TickTime t1 = t0;
WebRtc_Word64 max_time_us = 0;
WebRtc_Word64 max_time_reverse_us = 0;
WebRtc_Word64 min_time_us = 1e6;
WebRtc_Word64 min_time_reverse_us = 1e6;
while (simulating || feof(event_file) == 0) {
std::ostringstream trace_stream;
trace_stream << "Processed frames: " << reverse_count << " (reverse), "
<< primary_count << " (primary)";
SCOPED_TRACE(trace_stream.str());
if (simulating) {
if (far_file == NULL) {
event = kCaptureEvent;
} else {
if (event == kRenderEvent) {
event = kCaptureEvent;
} else {
event = kRenderEvent;
}
}
} else {
read_count = fread(&event, sizeof(event), 1, event_file);
if (read_count != 1) {
break;
}
//if (fread(&event, sizeof(event), 1, event_file) != 1) {
// break; // This is expected.
//}
}
if (event == kInitializeEvent || event == kResetEventDeprecated) {
ASSERT_EQ(1u,
fread(&sample_rate_hz, sizeof(sample_rate_hz), 1, event_file));
samples_per_channel = sample_rate_hz / 100;
ASSERT_EQ(1u,
fread(&device_sample_rate_hz,
sizeof(device_sample_rate_hz),
1,
event_file));
ASSERT_EQ(apm->kNoError,
apm->set_sample_rate_hz(sample_rate_hz));
ASSERT_EQ(apm->kNoError,
apm->echo_cancellation()->set_device_sample_rate_hz(
device_sample_rate_hz));
far_frame._frequencyInHz = sample_rate_hz;
near_frame._frequencyInHz = sample_rate_hz;
if (verbose) {
printf("Init at frame: %d (primary), %d (reverse)\n",
primary_count, reverse_count);
printf(" Sample rate: %d Hz\n", sample_rate_hz);
}
} else if (event == kRenderEvent) {
reverse_count++;
far_frame._audioChannel = num_render_channels;
far_frame._payloadDataLengthInSamples =
num_render_channels * samples_per_channel;
read_count = fread(far_frame._payloadData,
sizeof(WebRtc_Word16),
far_frame._payloadDataLengthInSamples,
far_file);
if (simulating) {
if (read_count != far_frame._payloadDataLengthInSamples) {
break; // This is expected.
}
} else {
ASSERT_EQ(read_count,
far_frame._payloadDataLengthInSamples);
}
if (perf_testing) {
t0 = TickTime::Now();
}
ASSERT_EQ(apm->kNoError,
apm->AnalyzeReverseStream(&far_frame));
if (perf_testing) {
t1 = TickTime::Now();
TickInterval tick_diff = t1 - t0;
acc_ticks += tick_diff;
if (tick_diff.Microseconds() > max_time_reverse_us) {
max_time_reverse_us = tick_diff.Microseconds();
}
if (tick_diff.Microseconds() < min_time_reverse_us) {
min_time_reverse_us = tick_diff.Microseconds();
}
}
} else if (event == kCaptureEvent) {
primary_count++;
near_frame._audioChannel = num_capture_input_channels;
near_frame._payloadDataLengthInSamples =
num_capture_input_channels * samples_per_channel;
read_count = fread(near_frame._payloadData,
sizeof(WebRtc_Word16),
near_frame._payloadDataLengthInSamples,
near_file);
near_read_samples += read_count;
if (progress && primary_count % 100 == 0) {
printf("%.0f%% complete\r",
(near_read_samples * 100.0) / near_size_samples);
fflush(stdout);
}
if (simulating) {
if (read_count != near_frame._payloadDataLengthInSamples) {
break; // This is expected.
}
delay_ms = 0;
drift_samples = 0;
} else {
ASSERT_EQ(read_count,
near_frame._payloadDataLengthInSamples);
// TODO(ajm): sizeof(delay_ms) for current files?
ASSERT_EQ(1u,
fread(&delay_ms, 2, 1, delay_file));
ASSERT_EQ(1u,
fread(&drift_samples, sizeof(drift_samples), 1, drift_file));
}
if (perf_testing) {
t0 = TickTime::Now();
}
// TODO(ajm): fake an analog gain while simulating.
int capture_level_in = capture_level;
ASSERT_EQ(apm->kNoError,
apm->gain_control()->set_stream_analog_level(capture_level));
ASSERT_EQ(apm->kNoError,
apm->set_stream_delay_ms(delay_ms));
ASSERT_EQ(apm->kNoError,
apm->echo_cancellation()->set_stream_drift_samples(drift_samples));
int err = apm->ProcessStream(&near_frame);
if (err == apm->kBadStreamParameterWarning) {
printf("Bad parameter warning. %s\n", trace_stream.str().c_str());
}
ASSERT_TRUE(err == apm->kNoError ||
err == apm->kBadStreamParameterWarning);
capture_level = apm->gain_control()->stream_analog_level();
stream_has_voice =
static_cast<int8_t>(apm->voice_detection()->stream_has_voice());
if (vad_out_file != NULL) {
ASSERT_EQ(1u, fwrite(&stream_has_voice,
sizeof(stream_has_voice),
1,
vad_out_file));
}
if (apm->gain_control()->mode() != GainControl::kAdaptiveAnalog) {
ASSERT_EQ(capture_level_in, capture_level);
}
if (perf_testing) {
t1 = TickTime::Now();
TickInterval tick_diff = t1 - t0;
acc_ticks += tick_diff;
if (tick_diff.Microseconds() > max_time_us) {
max_time_us = tick_diff.Microseconds();
}
if (tick_diff.Microseconds() < min_time_us) {
min_time_us = tick_diff.Microseconds();
}
}
ASSERT_EQ(near_frame._payloadDataLengthInSamples,
fwrite(near_frame._payloadData,
sizeof(WebRtc_Word16),
near_frame._payloadDataLengthInSamples,
out_file));
}
else {
FAIL() << "Event " << event << " is unrecognized";
}
}
if (verbose) {
printf("\nProcessed frames: %d (primary), %d (reverse)\n",
primary_count, reverse_count);
}
int8_t temp_int8;
if (far_file != NULL) {
read_count = fread(&temp_int8, sizeof(temp_int8), 1, far_file);
EXPECT_NE(0, feof(far_file)) << "Far-end file not fully processed";
}
read_count = fread(&temp_int8, sizeof(temp_int8), 1, near_file);
EXPECT_NE(0, feof(near_file)) << "Near-end file not fully processed";
if (!simulating) {
read_count = fread(&temp_int8, sizeof(temp_int8), 1, event_file);
EXPECT_NE(0, feof(event_file)) << "Event file not fully processed";
read_count = fread(&temp_int8, sizeof(temp_int8), 1, delay_file);
EXPECT_NE(0, feof(delay_file)) << "Delay file not fully processed";
read_count = fread(&temp_int8, sizeof(temp_int8), 1, drift_file);
EXPECT_NE(0, feof(drift_file)) << "Drift file not fully processed";
}
if (perf_testing) {
if (primary_count > 0) {
WebRtc_Word64 exec_time = acc_ticks.Milliseconds();
printf("\nTotal time: %.3f s, file time: %.2f s\n",
exec_time * 0.001, primary_count * 0.01);
printf("Time per frame: %.3f ms (average), %.3f ms (max),"
" %.3f ms (min)\n",
(exec_time * 1.0) / primary_count,
(max_time_us + max_time_reverse_us) / 1000.0,
(min_time_us + min_time_reverse_us) / 1000.0);
} else {
printf("Warning: no capture frames\n");
}
}
AudioProcessing::Destroy(apm);
apm = NULL;
}
int main(int argc, char* argv[])
{
void_main(argc, argv);
return 0;
}