|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include <math.h> |
|
|
|
#include <algorithm> |
|
#include <chrono> |
|
#include <cstring> |
|
#include <iostream> |
|
#include <iomanip> |
|
#include <memory> |
|
#include <set> |
|
#include <sstream> |
|
#include <thread> |
|
#include <tuple> |
|
|
|
#include "utils/wave_reader/waveReadWrite.hpp" |
|
#include "utils/ConfigReader.hpp" |
|
|
|
#include <nvAudioEffects.h> |
|
#include <map> |
|
|
|
namespace { |
|
|
|
const char kConfigEffectVariable[] = "effect"; |
|
const char kConfigSampleRateVariable[] = "sample_rate"; |
|
const char kConfigFileInputVariable[] = "input_wav_list"; |
|
const char kConfigFileInputFarendVariable[] = "input_farend_wav_list"; |
|
const char kConfigFileOutputVariable[] = "output_wav_list"; |
|
const char kConfigFileRTVariable[] = "real_time"; |
|
const char kConfigResetVariable[] = "reset"; |
|
const char kConfigFrameSize[] = "frame_size"; |
|
const char kConfigUseDefaultGpu[] = "use_default_gpu"; |
|
const char kConfigLogTarget[] = "log_target_list"; |
|
const char kConfigLogTargetFile[] = "log_target_file"; |
|
const char kConfigLogLevel[] = "log_level"; |
|
const char kConfigLogTargetFileDefault[] = "/tmp/nvAudioEffects_log.txt"; |
|
const char kConfigFileModelVariable[] = "model"; |
|
const char kConfigIntensityRatioVariable[] = "intensity_ratio"; |
|
const char kConfigVadEnable[] = "enable_vad"; |
|
const char kConfigChainedEffectGpuList[] = "chained_effect_gpu_list"; |
|
|
|
|
|
const std::vector<uint32_t> kAllowedSampleRates = { 8000, 16000, 48000 }; |
|
|
|
} |
|
|
|
struct StreamData { |
|
|
|
std::string input_wav_file; |
|
|
|
unsigned num_samples = 0; |
|
|
|
unsigned already_written = 0; |
|
|
|
std::vector<float>* input_wav_samples; |
|
|
|
std::vector<float>* input_farend_wav_samples; |
|
|
|
std::string output_wav_file; |
|
|
|
std::vector<std::unique_ptr<CWaveFileWrite>> wav_write; |
|
|
|
std::vector<int> file_end_offsets; |
|
|
|
int output_file_num; |
|
}; |
|
|
|
class EffectsDemoApp { |
|
public: |
|
bool run(const ConfigReader& config_reader); |
|
|
|
private: |
|
|
|
bool validateConfig(const ConfigReader& config_reader); |
|
|
|
bool writeOutputWav(const std::vector<float*>& output_samples, size_t num_samples_per_channel, |
|
int stream_index); |
|
|
|
uint32_t input_sample_rate_ = 0; |
|
|
|
uint32_t output_sample_rate_ = 0; |
|
|
|
std::vector<StreamData> stream_data_; |
|
|
|
uint32_t use_default_gpu_ = 0; |
|
|
|
bool real_time_ = false; |
|
|
|
float intensity_ratio_ = 1.0f; |
|
|
|
bool vad_supported_ = false; |
|
|
|
unsigned int output_channels_ = 1; |
|
}; |
|
|
|
bool EffectsDemoApp::validateConfig(const ConfigReader& config_reader) { |
|
if (config_reader.IsConfigValueAvailable(kConfigEffectVariable) == false) { |
|
std::cerr << "No " << kConfigEffectVariable << " variable found" << std::endl; |
|
return false; |
|
} |
|
|
|
if (config_reader.IsConfigValueAvailable(kConfigFileModelVariable) == false) { |
|
std::cerr << "No " << kConfigFileModelVariable << " variable found" << std::endl; |
|
return false; |
|
} |
|
|
|
if (config_reader.IsConfigValueAvailable(kConfigFileInputVariable) == false) { |
|
std::cerr << "No " << kConfigFileInputVariable << " variable found" << std::endl; |
|
return false; |
|
} |
|
|
|
if (config_reader.IsConfigValueAvailable(kConfigFileOutputVariable) == false) { |
|
std::cerr << "No " << kConfigFileOutputVariable << " variable found" << std::endl; |
|
return false; |
|
} |
|
|
|
std::string real_time; |
|
if (config_reader.GetConfigValue(kConfigFileRTVariable, &real_time) == false) { |
|
std::cerr << "No " << kConfigFileRTVariable << " variable found" << std::endl; |
|
return false; |
|
} |
|
|
|
if (real_time[0] != '0') { |
|
real_time_ = true; |
|
} |
|
|
|
if (config_reader.IsConfigValueAvailable(kConfigResetVariable)) { |
|
std::cout << "Reset available" << std::endl; |
|
} |
|
|
|
std::string use_default_gpu; |
|
if (config_reader.IsConfigValueAvailable(kConfigUseDefaultGpu) && |
|
config_reader.GetConfigValue(kConfigUseDefaultGpu, &use_default_gpu)) { |
|
use_default_gpu_ = std::strtoul(use_default_gpu.c_str(), nullptr, 0); |
|
std::cout << "Use default GPU: " << use_default_gpu_ << std::endl; |
|
} |
|
|
|
std::string intensity_ratio; |
|
float intensity_ratio_local; |
|
if (config_reader.GetConfigValue(kConfigIntensityRatioVariable, &intensity_ratio)) { |
|
intensity_ratio_local = std::strtof(intensity_ratio.c_str(), nullptr); |
|
if (intensity_ratio_local < 0.0f || intensity_ratio_local > 1.0f) { |
|
std::cerr << kConfigIntensityRatioVariable << " not supported" << std::endl; |
|
return false; |
|
} |
|
} else { |
|
intensity_ratio_local = 1.0f; |
|
} |
|
|
|
intensity_ratio_ = intensity_ratio_local; |
|
std::cout << "intensity ratio is ::" << intensity_ratio_ << "!!" << std::endl; |
|
return true; |
|
} |
|
|
|
bool EffectsDemoApp::writeOutputWav(const std::vector<float*>& output_samples, |
|
size_t num_samples_per_channel, int stream_index) { |
|
size_t to_write = num_samples_per_channel; |
|
size_t max_allowed = stream_data_[stream_index].num_samples * |
|
(output_sample_rate_/input_sample_rate_); |
|
size_t already_written = stream_data_[stream_index].already_written; |
|
size_t can_write = static_cast<size_t>(max_allowed - already_written); |
|
if (can_write == 0) { return true; } |
|
size_t will_write = std::min(to_write, can_write); |
|
|
|
for (int i = 0; i < output_channels_; i++) { |
|
if (!stream_data_[stream_index].wav_write[i]->writeChunk(output_samples[i], |
|
will_write * sizeof(float))) { |
|
std::cerr << "Could not write output to file " |
|
<< stream_data_[stream_index].output_wav_file << std::endl; |
|
return false; |
|
} |
|
} |
|
stream_data_[stream_index].already_written += will_write; |
|
return true; |
|
} |
|
|
|
void logger_cb(LoggingSeverity level, const char* log, void* userdata) { |
|
std::cout << "LOG" << '(' << LogSeverityToString(level) << ") " << log << '\n'; |
|
} |
|
|
|
inline LoggingSeverity StringToLogSeverity(std::string &severity) { |
|
if ("ERROR" == severity) return LOG_LEVEL_ERROR; |
|
if ("WARNING" == severity) return LOG_LEVEL_WARNING; |
|
if ("INFO" == severity) return LOG_LEVEL_INFO; |
|
return LOG_LEVEL_ERROR; |
|
} |
|
|
|
inline LoggingTarget StringToLogTarget(std::string &severity) { |
|
if ("NONE" == severity) return LOG_TARGET_NONE; |
|
if ("STDERR" == severity) return LOG_TARGET_STDERR; |
|
if ("FILE" == severity) return LOG_TARGET_FILE; |
|
if ("CALLBACK" == severity) return LOG_TARGET_CALLBACK; |
|
return LOG_TARGET_NONE; |
|
} |
|
|
|
bool EffectsDemoApp::run(const ConfigReader& config_reader) { |
|
if (validateConfig(config_reader) == false) { |
|
return false; |
|
} |
|
|
|
auto input_wav_list = config_reader.GetConfigValueList(kConfigFileInputVariable); |
|
auto output_wav_list = config_reader.GetConfigValueList(kConfigFileOutputVariable); |
|
|
|
const int kMaxSupportedInputs = 1024; |
|
if (input_wav_list.size() > kMaxSupportedInputs) { |
|
std::cerr << "This sample application supports a maximum of " << kMaxSupportedInputs << |
|
" input files (although more may be supported by the SDK depending on GPU," |
|
" please refer to the programming guide for limits and procedure for" |
|
" setting batch size in SDK)." << std::endl; |
|
return false; |
|
} |
|
std::vector<std::string> log_targets; |
|
if (config_reader.IsConfigValueAvailable(kConfigLogTarget)) { |
|
log_targets = config_reader.GetConfigValueList(kConfigLogTarget); |
|
} |
|
LoggingSeverity severity = LOG_LEVEL_INFO; |
|
int target = LOG_TARGET_STDERR; |
|
std::string log_file(kConfigLogTargetFileDefault); |
|
|
|
if (!log_targets.empty()) { |
|
target = LOG_TARGET_NONE; |
|
for (auto& item : log_targets) { |
|
target |= StringToLogTarget(item); |
|
} |
|
} |
|
|
|
if (target & LOG_TARGET_FILE) { |
|
if (config_reader.IsConfigValueAvailable(kConfigLogTargetFile)) { |
|
log_file = config_reader.GetConfigValue(kConfigLogTargetFile); |
|
} |
|
} |
|
|
|
if (config_reader.IsConfigValueAvailable(kConfigLogLevel)) { |
|
auto log_level = config_reader.GetConfigValue(kConfigLogLevel); |
|
if (!log_level.empty()) { |
|
severity = StringToLogSeverity(log_level); |
|
} |
|
} |
|
|
|
if (input_wav_list.size() > output_wav_list.size()) { |
|
std::cout << "Error: Input and output wav files list size mismatch found" << std::endl; |
|
return false; |
|
} |
|
|
|
std::vector<bool> reset_list(input_wav_list.size(), false); |
|
if (config_reader.IsConfigValueAvailable(kConfigResetVariable)) { |
|
auto config_list = config_reader.GetConfigValueList(kConfigResetVariable); |
|
for (auto& item : config_list) { |
|
char* p; |
|
unsigned int num = strtol(item.c_str(), &p, 10); |
|
if (*p != 0) { |
|
|
|
continue; |
|
} |
|
|
|
if (num < 1 || num > input_wav_list.size()) { |
|
std::cerr << "Error: Invalid stream specified for reset" <<std::endl; |
|
return false; |
|
} |
|
|
|
reset_list[num-1] = true; |
|
} |
|
} |
|
|
|
const unsigned num_streams = input_wav_list.size(); |
|
|
|
if (real_time_) { |
|
std::cout << "App will run in real time mode ..." << std::endl; |
|
} |
|
|
|
NvAFX_Status log_status; |
|
log_status = NvAFX_InitializeLogger(severity, target, log_file.c_str(), |
|
target & LOG_TARGET_CALLBACK ? logger_cb : nullptr, |
|
nullptr); |
|
|
|
if (log_status != NVAFX_STATUS_SUCCESS) { |
|
std::cerr << "NvAFX_InitializeLogger() failed" << std::endl; |
|
return false; |
|
} |
|
|
|
int num_effects; |
|
NvAFX_EffectSelector* supported_effects; |
|
if (NvAFX_GetEffectList(&num_effects, &supported_effects) != NVAFX_STATUS_SUCCESS) { |
|
std::cerr << "NvAFX_GetEffectList() failed" << std::endl; |
|
return false; |
|
} |
|
|
|
std::cout << "Total Effects supported: " << num_effects << std::endl; |
|
for (int i = 0; i < num_effects; ++i) { |
|
std::cout << "(" << i + 1 << ") " << supported_effects[i] << std::endl; |
|
} |
|
|
|
bool is_aec = false; |
|
|
|
NvAFX_Handle handle; |
|
std::vector<std::string> effects = config_reader.GetConfigValueList(kConfigEffectVariable); |
|
std::vector<std::string> sample_rate_str = config_reader.GetConfigValueList(kConfigSampleRateVariable); |
|
std::vector<uint32_t> sample_rates; |
|
for (auto& s: sample_rate_str) { |
|
sample_rates.push_back(std::strtoul(s.c_str(), nullptr, 0)); |
|
} |
|
|
|
std::string effect_name; |
|
if (effects.size() == 1) { |
|
|
|
if (sample_rates.size() != effects.size()) { |
|
std::cerr << "Expected single sample rate for single effect" << std::endl; |
|
return false; |
|
} |
|
input_sample_rate_ = sample_rates[0]; |
|
if (std::find(kAllowedSampleRates.begin(), kAllowedSampleRates.end(), input_sample_rate_) == |
|
kAllowedSampleRates.end()) { |
|
std::cerr << "Sample rate " << input_sample_rate_ << " not supported" << std::endl; |
|
return false; |
|
} |
|
NvAFX_Status status = NVAFX_STATUS_FAILED; |
|
if (effects[0] == "denoiser") { |
|
status = NvAFX_CreateEffect(NVAFX_EFFECT_DENOISER, &handle); |
|
} else if (effects[0] == "dereverb") { |
|
status = NvAFX_CreateEffect(NVAFX_EFFECT_DEREVERB, &handle); |
|
} else if (effects[0] == "dereverb_denoiser") { |
|
status = NvAFX_CreateEffect(NVAFX_EFFECT_DEREVERB_DENOISER, &handle); |
|
} else if (effects[0] == "aec") { |
|
status = NvAFX_CreateEffect(NVAFX_EFFECT_AEC, &handle); |
|
is_aec = true; |
|
} else if (effects[0] == "superres") { |
|
status = NvAFX_CreateEffect(NVAFX_EFFECT_SUPERRES, &handle); |
|
} else { |
|
std::cerr << "NvAFX_CreateEffect() failed. Invalid Effect Value : " << effects[0] << std::endl; |
|
return false; |
|
} |
|
if (status == NVAFX_UNSUPPORTED_RUNTIME) { |
|
float version = (CUDA_SUPPORTED_RUNTIME / 1000) + (CUDA_SUPPORTED_RUNTIME%100)/100.f; |
|
std::cerr << "Unsupported CUDA runtime (requires >= " << version << "). " |
|
"Please ensure that a driver supporting the required CUDA version is installed (or if using FCU, library path " |
|
"contains the correct CUDA compat libraries). For more details, please refer to the " |
|
"programming guide." << std::endl; |
|
return false; |
|
} else if (status != NVAFX_STATUS_SUCCESS) { |
|
std::cerr << "NvAFX_CreateEffect() failed" << std::endl; |
|
return false; |
|
} |
|
|
|
effect_name = effects[0]; |
|
const std::set<std::string> kVadSupportedEffects = {"denoiser", "dereverb_denoiser"}; |
|
vad_supported_ = (kVadSupportedEffects.find(effects[0]) != kVadSupportedEffects.end()) && |
|
config_reader.IsConfigValueAvailable(kConfigVadEnable) && |
|
std::strtoul(config_reader.GetConfigValue(kConfigVadEnable).c_str(), nullptr, 0); |
|
|
|
|
|
if (vad_supported_) { |
|
std::cout << "Enabling VAD" << std::endl; |
|
if (NvAFX_SetU32(handle, NVAFX_PARAM_ENABLE_VAD, 1) != NVAFX_STATUS_SUCCESS) { |
|
std::cerr << "Could not enable VAD" << std::endl; |
|
return false; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (NvAFX_SetU32(handle, NVAFX_PARAM_USE_DEFAULT_GPU, use_default_gpu_) != NVAFX_STATUS_SUCCESS) { |
|
std::cerr << "NvAFX_SetBool(NVAFX_PARAM_USE_DEFAULT_GPU " << ") failed" << std::endl; |
|
} |
|
|
|
status = NvAFX_SetU32(handle, NVAFX_PARAM_INPUT_SAMPLE_RATE, input_sample_rate_); |
|
if (status == NVAFX_STATUS_INVALID_PARAM) { |
|
|
|
status = NvAFX_SetU32(handle, NVAFX_PARAM_SAMPLE_RATE, input_sample_rate_); |
|
} |
|
if (status!= NVAFX_STATUS_SUCCESS) { |
|
std::cerr << "NvAFX_SetU32(Sample Rate: " << input_sample_rate_ << ") failed" << std::endl; |
|
return false; |
|
} |
|
|
|
} else { |
|
const std::map<std::tuple<std::string, std::string, uint32_t, uint32_t>, NvAFX_EffectSelector> supported_configs = |
|
{ |
|
|
|
{std::make_tuple(std::string("denoiser"), std::string("superres"), 16000, 16000), NVAFX_CHAINED_EFFECT_DENOISER_16k_SUPERRES_16k_TO_48k}, |
|
{std::make_tuple(std::string("dereverb"), std::string("superres"), 16000, 16000), NVAFX_CHAINED_EFFECT_DEREVERB_16k_SUPERRES_16k_TO_48k}, |
|
{std::make_tuple(std::string("dereverb_denoiser"), std::string("superres"), 16000, 16000), |
|
NVAFX_CHAINED_EFFECT_DEREVERB_DENOISER_16k_SUPERRES_16k_TO_48k}, |
|
|
|
|
|
{std::make_tuple("superres", "denoiser", 8000, 16000), NVAFX_CHAINED_EFFECT_SUPERRES_8k_TO_16k_DENOISER_16k }, |
|
{std::make_tuple("superres", "dereverb", 8000, 16000), NVAFX_CHAINED_EFFECT_SUPERRES_8k_TO_16k_DEREVERB_16k }, |
|
{std::make_tuple("superres", "dereverb_denoiser", 8000, 16000), |
|
NVAFX_CHAINED_EFFECT_SUPERRES_8k_TO_16k_DEREVERB_DENOISER_16k }, |
|
}; |
|
auto effect_config = supported_configs.find(std::make_tuple(effects[0], effects[1], sample_rates[0], sample_rates[1])); |
|
if (effect_config == supported_configs.end()) { |
|
std::cerr << "Unsupported effect chain" <<std::endl; |
|
return false; |
|
} |
|
auto status = NvAFX_CreateChainedEffect(effect_config->second,&handle); |
|
if (status == NVAFX_UNSUPPORTED_RUNTIME) { |
|
float version = (CUDA_SUPPORTED_RUNTIME / 1000) + (CUDA_SUPPORTED_RUNTIME%100)/100.f; |
|
std::cerr << "Unsupported CUDA runtime (requires >= " << version << "). " |
|
"Please ensure that a driver supporting the required CUDA version is installed (or if using FCU, library path " |
|
"contains the correct CUDA compat libraries). For more details, please refer to the " |
|
"programming guide." << std::endl; |
|
return false; |
|
} else if (status != NVAFX_STATUS_SUCCESS) { |
|
std::cerr << "Could not create effect" << std::endl; |
|
return false; |
|
} |
|
input_sample_rate_ = sample_rates[0]; |
|
|
|
effect_name = "Chained Effect (" + effects[0] + " + " + effects[1] + ")"; |
|
|
|
if (config_reader.IsConfigValueAvailable(kConfigChainedEffectGpuList)) { |
|
std::vector<std::string> gpu_list = config_reader.GetConfigValueList(kConfigChainedEffectGpuList); |
|
std::vector<uint32_t> gpus; |
|
for (auto& s: gpu_list) { |
|
gpus.push_back(std::strtoul(s.c_str(), nullptr, 0)); |
|
} |
|
|
|
NvAFX_SetU32List(handle, NVAFX_PARAM_CHAINED_EFFECT_GPU_LIST, gpus.data(), gpus.size()); |
|
} |
|
|
|
} |
|
|
|
unsigned num_input_samples_per_frame = 0; |
|
|
|
|
|
unsigned num_output_samples_per_frame = 0; |
|
if (config_reader.IsConfigValueAvailable(kConfigFrameSize)) { |
|
auto config_value = config_reader.GetConfigValue(kConfigFrameSize); |
|
unsigned frame_size = std::strtoul(config_value.c_str(), nullptr, 0); |
|
|
|
num_input_samples_per_frame = (input_sample_rate_ * frame_size) / 1000; |
|
} |
|
|
|
|
|
std::vector<std::string> input_farend_wav_list; |
|
if (is_aec) { |
|
input_farend_wav_list = config_reader.GetConfigValueList(kConfigFileInputFarendVariable); |
|
if (input_farend_wav_list.size() != input_wav_list.size()) { |
|
std::cerr << "AEC effect requires farend audio as input in addition to nearend audio." |
|
"Please ensure that the config value \"" << kConfigFileInputFarendVariable << |
|
"\" is present and has correct number of files" << std::endl; |
|
return false; |
|
} |
|
} |
|
|
|
NvAFX_Status status; |
|
|
|
std::vector<std::string> model_files = config_reader.GetConfigValueList(kConfigFileModelVariable); |
|
|
|
std::unique_ptr<char*[]> model_files_param(new char*[model_files.size()]); |
|
for (int i = 0; i < model_files.size(); i++) { |
|
model_files_param[i] = (char*) model_files[i].data(); |
|
} |
|
if (NvAFX_SetStringList(handle, NVAFX_PARAM_MODEL_PATH, (const char**)model_files_param.get(), |
|
model_files.size()) |
|
!= NVAFX_STATUS_SUCCESS) { |
|
std::cerr << "NvAFX_SetString() failed" << std::endl; |
|
return false; |
|
} |
|
|
|
if (NvAFX_SetU32(handle, NVAFX_PARAM_NUM_STREAMS, num_streams) != NVAFX_STATUS_SUCCESS) { |
|
std::cerr << "NvAFX_SetU32(NVAFX_PARAM_NUM_STREAMS) failed" << std::endl; |
|
return false; |
|
} |
|
|
|
unsigned int list_size = 0; |
|
std::unique_ptr<unsigned int[]> supported_list = nullptr; |
|
auto ret = NvAFX_GetU32List(handle, NVAFX_PARAM_SUPPORTED_NUM_SAMPLES_PER_FRAME, |
|
supported_list.get(), &list_size); |
|
if (ret != NVAFX_STATUS_OUTPUT_BUFFER_TOO_SMALL) { |
|
std::cerr << "NvAFX_GetU32List(NVAFX_PARAM_SUPPORTED_NUM_SAMPLES_PER_FRAME) failed." |
|
<< std::endl; |
|
return false; |
|
} |
|
supported_list.reset(new unsigned int[list_size]); |
|
if (NvAFX_GetU32List(handle, NVAFX_PARAM_SUPPORTED_NUM_SAMPLES_PER_FRAME, |
|
supported_list.get(), &list_size) != NVAFX_STATUS_SUCCESS) { |
|
std::cerr << "NvAFX_GetU32List(NVAFX_PARAM_SUPPORTED_NUM_SAMPLES_PER_FRAME) failed." |
|
<< std::endl; |
|
return false; |
|
} |
|
|
|
if (config_reader.IsConfigValueAvailable(kConfigFrameSize)) { |
|
auto pointer = std::find(supported_list.get(), supported_list.get() + list_size, |
|
num_input_samples_per_frame); |
|
if (pointer == supported_list.get() + list_size) { |
|
std::ostringstream oss; |
|
std::string separator(""); |
|
oss << "'"; |
|
for (unsigned i = 0; i < list_size; ++i) { |
|
oss << separator << (supported_list[i] * 1000) / input_sample_rate_; |
|
separator = ", "; |
|
} |
|
oss << "'."; |
|
std::cerr << "Supplied value for " << kConfigFrameSize << " is not supported. Supplied value: " |
|
<< config_reader.GetConfigValue(kConfigFrameSize) << "." << " Supported values: " |
|
<< oss.str() << std::endl; |
|
return false; |
|
} |
|
} else { |
|
num_input_samples_per_frame = supported_list[0]; |
|
} |
|
|
|
status = NvAFX_SetU32(handle, NVAFX_PARAM_NUM_SAMPLES_PER_INPUT_FRAME, num_input_samples_per_frame); |
|
if (status == NVAFX_STATUS_INVALID_PARAM) { |
|
|
|
status = NvAFX_SetU32(handle, NVAFX_PARAM_NUM_SAMPLES_PER_FRAME, num_input_samples_per_frame); |
|
} |
|
if (status!= NVAFX_STATUS_SUCCESS) { |
|
std::cerr << "NvAFX_SetU32(NVAFX_PARAM_NUM_SAMPLES_PER_FRAME) failed" << std::endl; |
|
return false; |
|
} |
|
|
|
|
|
status = NvAFX_SetFloat(handle, NVAFX_PARAM_INTENSITY_RATIO, intensity_ratio_); |
|
if (status != NVAFX_STATUS_SUCCESS && status != NVAFX_STATUS_INVALID_PARAM) { |
|
std::cerr << "NvAFX_SetFloat(NVAFX_PARAM_INTENSITY_RATIO) failed" << std::endl; |
|
} |
|
|
|
std::cout << "Loading effect" << " ... "; |
|
if (NvAFX_Load(handle) != NVAFX_STATUS_SUCCESS) { |
|
std::cerr << "NvAFX_Load() failed" << std::endl; |
|
return false; |
|
} |
|
std::cout << "Done" << std::endl; |
|
|
|
|
|
status = NvAFX_GetU32(handle, NVAFX_PARAM_NUM_SAMPLES_PER_OUTPUT_FRAME, |
|
&num_output_samples_per_frame); |
|
if (status != NVAFX_STATUS_SUCCESS) { |
|
if (status == NVAFX_STATUS_INVALID_PARAM) { |
|
|
|
num_output_samples_per_frame = num_input_samples_per_frame; |
|
} else { |
|
std::cerr << "NvAFX_GetU32() failed: NVAFX_PARAM_NUM_SAMPLES_PER_OUTPUT_FRAME" << std::endl; |
|
return false; |
|
} |
|
} |
|
|
|
|
|
status = NvAFX_GetU32(handle, NVAFX_PARAM_OUTPUT_SAMPLE_RATE, &output_sample_rate_); |
|
if (status != NVAFX_STATUS_SUCCESS) { |
|
if (status == NVAFX_STATUS_INVALID_PARAM) { |
|
|
|
output_sample_rate_ = input_sample_rate_; |
|
} else { |
|
std::cerr << "NvAFX_GetU32() failed: NVAFX_PARAM_OUTPUT_SAMPLE_RATE" << std::endl; |
|
return false; |
|
} |
|
} |
|
|
|
|
|
unsigned num_input_channels, num_output_channels; |
|
status = NvAFX_GetU32(handle, NVAFX_PARAM_NUM_INPUT_CHANNELS, &num_input_channels); |
|
if (status != NVAFX_STATUS_SUCCESS) { |
|
if (status == NVAFX_STATUS_INVALID_PARAM) { |
|
if (NvAFX_GetU32(handle, NVAFX_PARAM_NUM_CHANNELS, &num_input_channels) != NVAFX_STATUS_SUCCESS) { |
|
std::cerr << "NvAFX_GetU32(NVAFX_PARAM_NUM_CHANNELS) failed" << std::endl; |
|
return false; |
|
} else { |
|
num_output_channels = num_input_channels; |
|
} |
|
} else { |
|
std::cerr << "NvAFX_GetU32() failed" << std::endl; |
|
return false; |
|
} |
|
} else if (NvAFX_GetU32(handle, NVAFX_PARAM_NUM_OUTPUT_CHANNELS, &num_output_channels) != |
|
NVAFX_STATUS_SUCCESS) { |
|
std::cerr << "NvAFX_GetU32(NVAFX_PARAM_NUM_OUTPUT_CHANNELS) failed" << std::endl; |
|
return false; |
|
} |
|
|
|
output_channels_ = num_output_channels; |
|
|
|
std::cout << "Effect properties:" << std::endl |
|
<< " Effect : " << effect_name << std::endl |
|
<< " Input Channels : " << num_input_channels << std::endl |
|
<< " Input Sample Rate : " << input_sample_rate_ << std::endl |
|
<< " Output Sample Rate : " << output_sample_rate_ << std::endl |
|
<< " Samples per frame : " << num_input_samples_per_frame << std::endl |
|
<< " Number of streams : " << num_streams << std::endl; |
|
|
|
size_t max_num_input_samples = 0; |
|
|
|
for (unsigned i = 0; i < num_streams; ++i) { |
|
StreamData data; |
|
data.input_wav_file = input_wav_list[i]; |
|
|
|
if (!ReadWavFile(input_wav_list[i], input_sample_rate_, &data.input_wav_samples, |
|
&data.num_samples, &data.file_end_offsets, num_input_samples_per_frame)) { |
|
std::cerr << "Unable to read wav file: " << input_wav_list[i] << std::endl; |
|
if (errno == EMFILE) { |
|
std::cerr << "Open file limit reached. Please increase file limit using the ulimit " |
|
"utility (for example, \"ulimit -n 20000\"). Please refer to the " |
|
"documentation of the ulimit utility for more details." << std::endl; |
|
} |
|
return false; |
|
} |
|
|
|
|
|
if (max_num_input_samples < data.input_wav_samples->size()) { |
|
max_num_input_samples = data.input_wav_samples->size(); |
|
} |
|
|
|
if (is_aec) { |
|
unsigned int num_samples = 0; |
|
std::vector<int> end_offsets; |
|
|
|
if (!ReadWavFile(input_farend_wav_list[i], input_sample_rate_, &data.input_farend_wav_samples, |
|
&num_samples, &end_offsets, num_input_samples_per_frame)) { |
|
std::cerr << "Unable to read wav file: " << input_farend_wav_list[i] << std::endl; |
|
if (errno == EMFILE) { |
|
std::cerr << "Open file limit reached. Please increase file limit using the ulimit " |
|
"utility (for example, \"ulimit -n 20000\"). Please refer to the " |
|
"documentation of the ulimit utility for more details." << std::endl; |
|
} |
|
return false; |
|
} |
|
|
|
if (num_samples != data.num_samples || end_offsets != data.file_end_offsets) { |
|
std::cerr << "Input farend file specification does not match nearend file specification." |
|
"Farend and Nearend files must have the same number of samples"; |
|
return false; |
|
} |
|
} |
|
|
|
data.output_wav_file = output_wav_list[i]; |
|
if (output_channels_ > 1) { |
|
auto filename_wo_ext = output_wav_list[i].substr(0, output_wav_list[i].find_last_of(".")); |
|
|
|
for (int ch = 0; ch < output_channels_; ch++) { |
|
std::string filename = filename_wo_ext + "_ch" + std::to_string(ch + 1) + ".wav"; |
|
data.wav_write.emplace_back( |
|
std::unique_ptr<CWaveFileWrite>(new CWaveFileWrite(filename, |
|
output_sample_rate_, |
|
1, 32, true))); |
|
|
|
} |
|
} else { |
|
data.wav_write.emplace_back(std::unique_ptr<CWaveFileWrite>(new CWaveFileWrite(output_wav_list[i], output_sample_rate_, |
|
1, 32, true))); |
|
} |
|
for (int ch = 0; ch < num_output_channels; ch++) { |
|
if (!data.wav_write[ch]->initFile()) { |
|
std::cerr << "Unable to open file for writing: " |
|
<< data.wav_write[ch]->getFileName() << std::endl; |
|
if (errno == EMFILE) { |
|
std::cerr << "Open file limit reached. Please increase file limit using the ulimit " |
|
"utility (for example, \"ulimit -n 20000\"). Please refer to the " |
|
"documentation of the ulimit utility for more details." << std::endl; |
|
} |
|
return false; |
|
} |
|
|
|
} |
|
data.output_file_num = 0; |
|
|
|
stream_data_.push_back(std::move(data)); |
|
#ifndef ENABLE_PERF_DUMP |
|
std::cout << "Input wav file: " << input_wav_list[i] << std::endl |
|
<< "Total " << data.num_samples << " samples read" << std::endl; |
|
#endif |
|
} |
|
|
|
|
|
for (auto& data : stream_data_) { |
|
data.input_wav_samples->resize(max_num_input_samples); |
|
} |
|
|
|
float frame_in_secs = static_cast<float>(num_input_samples_per_frame) / |
|
static_cast<float>(input_sample_rate_); |
|
float total_run_time = 0.f; |
|
float total_audio_duration = 0.f; |
|
float checkpoint = 0.1f; |
|
float expected_audio_duration = static_cast<float>(max_num_input_samples) / |
|
static_cast<float>(input_sample_rate_); |
|
std::vector<float> input_frame(num_input_channels * num_input_samples_per_frame * num_streams); |
|
std::vector<float> output_frame(num_output_channels * num_output_samples_per_frame * num_streams); |
|
|
|
std::string progress_bar = "[ ] "; |
|
std::cout << "Processed: " << progress_bar << "0%\r"; |
|
std::cout.flush(); |
|
|
|
std::vector<NvAFX_Bool> bitmap(input_wav_list.size(), NVAFX_FALSE); |
|
|
|
|
|
for (size_t offset = 0; offset < max_num_input_samples; offset += num_input_samples_per_frame) { |
|
|
|
bool should_reset = false; |
|
for (unsigned i = 0; i < num_streams; ++i) { |
|
float *in = stream_data_[i].input_wav_samples->data() + offset; |
|
std::copy(in, in + num_input_samples_per_frame, &input_frame[i * num_input_samples_per_frame]); |
|
if (is_aec) { |
|
in = stream_data_[i].input_farend_wav_samples->data() + offset; |
|
std::copy(in, in + num_input_samples_per_frame, &input_frame[(num_streams + i) * num_input_samples_per_frame]); |
|
} |
|
|
|
|
|
if (stream_data_[i].file_end_offsets.size() && |
|
offset == static_cast<unsigned>(stream_data_[i].file_end_offsets[0])) { |
|
stream_data_[i].file_end_offsets.erase(stream_data_[i].file_end_offsets.begin()); |
|
if (stream_data_[i].file_end_offsets.size()) { |
|
|
|
|
|
if (output_channels_ > 1) { |
|
uint32_t output_num = ++stream_data_[i].output_file_num; |
|
auto filename_wo_ext = output_wav_list[i].substr(0, output_wav_list[i].find_last_of("_")); |
|
for (int ch = 0; ch < output_channels_; ch++) { |
|
stream_data_[i].wav_write[ch]->commitFile(); |
|
std::string filename = filename_wo_ext + + "_" + std::to_string(output_num) + |
|
"_ch" + std::to_string(ch + 1) + ".wav"; |
|
stream_data_[i].wav_write[ch] = std::unique_ptr<CWaveFileWrite>(new CWaveFileWrite(filename, output_sample_rate_, |
|
1, 32, true)); |
|
if (!stream_data_[i].wav_write[ch]->initFile()) { |
|
std::cerr << "Unable to open file for writing: " << filename; |
|
return false; |
|
} |
|
} |
|
} else { |
|
auto ext = output_wav_list[i].find_last_of("."); |
|
std::string filename = output_wav_list[i].substr(0, ext) + "_" + |
|
std::to_string(++stream_data_[i].output_file_num) + ".wav"; |
|
stream_data_[i].wav_write[0] = std::unique_ptr<CWaveFileWrite>(new CWaveFileWrite(filename, output_sample_rate_, |
|
1, 32, true)); |
|
|
|
if (!stream_data_[i].wav_write[0]->initFile()) { |
|
std::cerr << "Unable to open file for writing: " << filename; |
|
return false; |
|
} |
|
} |
|
|
|
|
|
if (reset_list[i]) { |
|
should_reset = true; |
|
bitmap[i] = NVAFX_TRUE; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (should_reset) { |
|
if (NvAFX_Reset(handle, bitmap.data(), input_wav_list.size()) != NVAFX_STATUS_SUCCESS) { |
|
std::cerr << "Reset failed" << std::endl; |
|
return false; |
|
} |
|
memset(bitmap.data(), NVAFX_FALSE, sizeof(NvAFX_Bool) * bitmap.size()); |
|
} |
|
|
|
const float* input[2]; |
|
float* output[2]; |
|
input[0] = input_frame.data(); |
|
if (is_aec) { |
|
input[1] = &input_frame[input_frame.size() / num_input_channels]; |
|
} |
|
|
|
output[0] = output_frame.data(); |
|
if (num_output_channels > 1) { |
|
output[1] = output_frame.data() + output_frame.size()/2; |
|
} |
|
|
|
auto start_tick = std::chrono::high_resolution_clock::now(); |
|
if (NvAFX_Run(handle, input, output, num_input_samples_per_frame, num_input_channels) != NVAFX_STATUS_SUCCESS) { |
|
std::cerr << "NvAFX_Run() failed" << std::endl; |
|
return false; |
|
} |
|
|
|
auto run_end_tick = std::chrono::high_resolution_clock::now(); |
|
total_run_time += (std::chrono::duration<float>(run_end_tick - start_tick)).count(); |
|
total_audio_duration += frame_in_secs; |
|
|
|
if ((total_audio_duration / expected_audio_duration) > checkpoint) { |
|
progress_bar[checkpoint * 10] = '='; |
|
std::cout << "Processed: " << progress_bar << checkpoint * 100.f << "% "; |
|
std::cout << (checkpoint >=1 ? "\n" : "\r"); |
|
std::cout.flush(); |
|
checkpoint += 0.1f; |
|
} |
|
|
|
for (unsigned i = 0; i < num_streams; ++i) { |
|
unsigned idx = i * num_output_samples_per_frame; |
|
std::vector<float*> outputs = {}; |
|
unsigned size_per_channel = output_frame.size()/output_channels_; |
|
for (int ch = 0; ch < output_channels_; ch++) { |
|
outputs.push_back(&output_frame[idx + ch * size_per_channel]); |
|
} |
|
if (!writeOutputWav(outputs, num_output_samples_per_frame, i)) { |
|
return false; |
|
} |
|
} |
|
|
|
if (real_time_) { |
|
auto end_tick = std::chrono::high_resolution_clock::now(); |
|
std::chrono::duration<float> elapsed = end_tick - start_tick; |
|
float sleep_time_secs = frame_in_secs - elapsed.count(); |
|
std::this_thread::sleep_for(std::chrono::milliseconds(static_cast<int>(sleep_time_secs * 1000))); |
|
} |
|
} |
|
|
|
std::cout << "Processing time " << std::setprecision(2) << total_run_time << " secs for " |
|
<< total_audio_duration << std::setprecision(2) << " secs audio file (" << total_run_time / total_audio_duration |
|
<< " secs processing time per sec of audio)" << std::endl; |
|
if (real_time_) { |
|
std::cout << "Note: App ran in real time mode i.e. simulated the input data rate of a mic" << std::endl |
|
<< "'Processing time' could be less then actual run time" << std::endl; |
|
} |
|
|
|
for (auto& data : stream_data_) { |
|
for (int ch = 0; ch < output_channels_; ch++) { |
|
data.wav_write[ch]->commitFile(); |
|
} |
|
} |
|
|
|
std::cout << "Output wav files written. " << std::endl; |
|
if (NvAFX_DestroyEffect(handle) != NVAFX_STATUS_SUCCESS) { |
|
std::cerr << "NvAFX_Release() failed" << std::endl; |
|
return false; |
|
} |
|
|
|
if (NvAFX_UninitializeLogger() != NVAFX_STATUS_SUCCESS) { |
|
std::cerr << "NvAFX_UninitializeLogger() failed" << std::endl; |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
void ShowHelpAndExit(const char* bad_option) { |
|
std::ostringstream oss; |
|
if (bad_option) { |
|
oss << "Error parsing \"" << bad_option << "\"" << std::endl; |
|
} |
|
std::cout << "Command Line Options:" << std::endl |
|
<< "-c Config file" << std::endl; |
|
|
|
std::cout << oss.str(); |
|
exit(0); |
|
} |
|
|
|
void ParseCommandLine(int argc, char* argv[], std::string* config_file) { |
|
if (argc == 1) { |
|
ShowHelpAndExit(nullptr); |
|
} |
|
|
|
for (int i = 1; i < argc; i++) { |
|
if (!strcasecmp(argv[i], "-h")) { |
|
ShowHelpAndExit(nullptr); |
|
} |
|
if (!strcasecmp(argv[i], "-c")) { |
|
if (++i == argc || !config_file->empty()) { |
|
ShowHelpAndExit("-f"); |
|
} |
|
config_file->assign(argv[i]); |
|
continue; |
|
} |
|
|
|
ShowHelpAndExit(argv[i]); |
|
} |
|
} |
|
|
|
int main(int argc, char *argv[]) { |
|
std::string config_file; |
|
ParseCommandLine(argc, argv, &config_file); |
|
|
|
ConfigReader config_reader; |
|
if (config_reader.Load(config_file) == false) { |
|
std::cerr << "Config file load failed" << std::endl; |
|
return -1; |
|
} |
|
|
|
EffectsDemoApp app; |
|
if (app.run(config_reader)) { return 0; } |
|
else { return -1; } |
|
} |
|
|