TeaSpeak-Client/native/serverconnection/src/audio/js/AudioProcessor.cpp

398 lines
16 KiB
C++

//
// Created by WolverinDEV on 28/03/2021.
//
#include "./AudioProcessor.h"
#include "../../logger.h"
#include <NanStrings.h>
using namespace tc::audio;
NAN_MODULE_INIT(AudioProcessorWrapper::Init) {
auto klass = Nan::New<v8::FunctionTemplate>(AudioProcessorWrapper::NewInstance);
klass->SetClassName(Nan::New("AudioProcessor").ToLocalChecked());
klass->InstanceTemplate()->SetInternalFieldCount(1);
Nan::SetPrototypeMethod(klass, "get_config", AudioProcessorWrapper::get_config);
Nan::SetPrototypeMethod(klass, "apply_config", AudioProcessorWrapper::apply_config);
Nan::SetPrototypeMethod(klass, "get_statistics", AudioProcessorWrapper::get_statistics);
constructor().Reset(Nan::GetFunction(klass).ToLocalChecked());
}
NAN_METHOD(AudioProcessorWrapper::NewInstance) {
if(!info.IsConstructCall()) {
Nan::ThrowError("invalid invoke!");
}
}
AudioProcessorWrapper::AudioProcessorWrapper(const std::shared_ptr<AudioProcessor> &processor) {
log_allocate("AudioProcessorWrapper", this);
this->registered_observer = new Observer{this};
this->weak_processor = processor;
processor->register_process_observer(this->registered_observer);
}
AudioProcessorWrapper::~AudioProcessorWrapper() noexcept {
log_free("AudioProcessorWrapper", this);
if(auto processor{this->weak_processor.lock()}; processor) {
processor->unregister_process_observer(this->registered_observer);
}
delete this->registered_observer;
}
#define PUT_VALUE(key, value) \
result->Set(context, Nan::LocalStringUTF8(#key), value).Check()
#define PUT_CONFIG(path) \
PUT_VALUE(path, Nan::New(config.path))
#define LOAD_CONFIG(path, ...) \
do { \
if(!load_config_value(context, js_config, #path, config.path, ##__VA_ARGS__)) { \
return; \
} \
} while(0)
NAN_METHOD(AudioProcessorWrapper::get_config) {
auto handle = Nan::ObjectWrap::Unwrap<AudioProcessorWrapper>(info.Holder());
auto processor = handle->weak_processor.lock();
if(!processor) {
Nan::ThrowError("processor passed away");
return;
}
auto config = processor->get_config();
auto result = Nan::New<v8::Object>();
auto context = info.GetIsolate()->GetCurrentContext();
PUT_CONFIG(pipeline.maximum_internal_processing_rate);
PUT_CONFIG(pipeline.multi_channel_render);
PUT_CONFIG(pipeline.multi_channel_capture);
PUT_CONFIG(pre_amplifier.enabled);
PUT_CONFIG(pre_amplifier.fixed_gain_factor);
PUT_CONFIG(high_pass_filter.enabled);
PUT_CONFIG(high_pass_filter.apply_in_full_band);
PUT_CONFIG(echo_canceller.enabled);
PUT_CONFIG(echo_canceller.mobile_mode);
PUT_CONFIG(echo_canceller.export_linear_aec_output); /* TODO: Consider removing? */
PUT_CONFIG(echo_canceller.enforce_high_pass_filtering);
PUT_CONFIG(noise_suppression.enabled);
switch (config.noise_suppression.level) {
using Level = webrtc::AudioProcessing::Config::NoiseSuppression::Level;
case Level::kLow:
PUT_VALUE(noise_suppression.level, Nan::LocalStringUTF8("low"));
break;
case Level::kModerate:
PUT_VALUE(noise_suppression.level, Nan::LocalStringUTF8("moderate"));
break;
case Level::kHigh:
PUT_VALUE(noise_suppression.level, Nan::LocalStringUTF8("high"));
break;
case Level::kVeryHigh:
PUT_VALUE(noise_suppression.level, Nan::LocalStringUTF8("very-high"));
break;
default:
PUT_VALUE(noise_suppression.level, Nan::LocalStringUTF8("unknown"));
break;
}
PUT_CONFIG(noise_suppression.analyze_linear_aec_output_when_available);
PUT_CONFIG(transient_suppression.enabled);
PUT_CONFIG(voice_detection.enabled);
PUT_CONFIG(gain_controller1.enabled);
switch (config.gain_controller1.mode) {
using Mode = webrtc::AudioProcessing::Config::GainController1::Mode;
case Mode::kAdaptiveAnalog:
PUT_VALUE(gain_controller1.mode, Nan::LocalStringUTF8("adaptive-analog"));
break;
case Mode::kAdaptiveDigital:
PUT_VALUE(gain_controller1.mode, Nan::LocalStringUTF8("adaptive-digital"));
break;
case Mode::kFixedDigital:
PUT_VALUE(gain_controller1.mode, Nan::LocalStringUTF8("fixed-digital"));
break;
default:
PUT_VALUE(gain_controller1.mode, Nan::LocalStringUTF8("unknown"));
break;
}
PUT_CONFIG(gain_controller1.target_level_dbfs);
PUT_CONFIG(gain_controller1.compression_gain_db);
PUT_CONFIG(gain_controller1.enable_limiter);
PUT_CONFIG(gain_controller1.analog_level_minimum);
PUT_CONFIG(gain_controller1.analog_level_maximum);
PUT_CONFIG(gain_controller1.analog_gain_controller.enabled);
PUT_CONFIG(gain_controller1.analog_gain_controller.startup_min_volume);
PUT_CONFIG(gain_controller1.analog_gain_controller.clipped_level_min);
PUT_CONFIG(gain_controller1.analog_gain_controller.enable_agc2_level_estimator);
PUT_CONFIG(gain_controller1.analog_gain_controller.enable_digital_adaptive);
PUT_CONFIG(gain_controller2.enabled);
PUT_CONFIG(gain_controller2.fixed_digital.gain_db);
PUT_CONFIG(gain_controller2.adaptive_digital.enabled);
switch(config.gain_controller2.adaptive_digital.level_estimator) {
using LevelEstimator = webrtc::AudioProcessing::Config::GainController2::LevelEstimator;
case LevelEstimator::kPeak:
PUT_VALUE(gain_controller2.adaptive_digital.level_estimator, Nan::LocalStringUTF8("peak"));
break;
case LevelEstimator::kRms:
PUT_VALUE(gain_controller2.adaptive_digital.level_estimator, Nan::LocalStringUTF8("rms"));
break;
default:
PUT_VALUE(gain_controller2.adaptive_digital.level_estimator, Nan::LocalStringUTF8("unknown"));
break;
}
PUT_CONFIG(gain_controller2.adaptive_digital.vad_probability_attack);
PUT_CONFIG(gain_controller2.adaptive_digital.level_estimator_adjacent_speech_frames_threshold);
PUT_CONFIG(gain_controller2.adaptive_digital.use_saturation_protector);
PUT_CONFIG(gain_controller2.adaptive_digital.initial_saturation_margin_db);
PUT_CONFIG(gain_controller2.adaptive_digital.extra_saturation_margin_db);
PUT_CONFIG(gain_controller2.adaptive_digital.gain_applier_adjacent_speech_frames_threshold);
PUT_CONFIG(gain_controller2.adaptive_digital.max_gain_change_db_per_second);
PUT_CONFIG(gain_controller2.adaptive_digital.max_output_noise_level_dbfs);
PUT_CONFIG(residual_echo_detector.enabled);
PUT_CONFIG(level_estimation.enabled);
PUT_CONFIG(rnnoise.enabled);
PUT_CONFIG(artificial_stream_delay);
info.GetReturnValue().Set(result);
}
template <typename T, typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
inline bool load_config_value(
const v8::Local<v8::Context>& context,
const v8::Local<v8::Object>& js_config,
const std::string_view& key,
T& value_ref,
T min_value = std::numeric_limits<T>::min(),
T max_value = std::numeric_limits<T>::max()
) {
auto maybe_value = js_config->Get(context, Nan::LocalStringUTF8(key));
if(maybe_value.IsEmpty() || maybe_value.ToLocalChecked()->IsNullOrUndefined()) {
return true;
}
double value;
if(maybe_value.ToLocalChecked()->IsNumber()) {
value = maybe_value.ToLocalChecked()->NumberValue(context).ToChecked();
} else if(maybe_value.ToLocalChecked()->IsBoolean()) {
value = maybe_value.ToLocalChecked()->BooleanValue(v8::Isolate::GetCurrent());
} else {
Nan::ThrowError(Nan::LocalStringUTF8("property " + std::string{key} + " isn't a number or boolean"));
return false;
}
if(std::numeric_limits<T>::is_integer && (double) (T) value != value) {
Nan::ThrowError(Nan::LocalStringUTF8("property " + std::string{key} + " isn't an integer"));
return false;
}
if((T) value < min_value) {
Nan::ThrowError(Nan::LocalStringUTF8("property " + std::string{key} + " exceeds min value of " + std::to_string((T) min_value) + " (value: " + std::to_string((T) value) + ")"));
return false;
}
if((T) value > (double) max_value) {
Nan::ThrowError(Nan::LocalStringUTF8("property " + std::string{key} + " exceeds max value of " + std::to_string((T) max_value) + " (value: " + std::to_string((T) value) + ")"));
return false;
}
value_ref = value;
return true;
}
template <size_t kValueSize, typename T>
inline bool load_config_enum(
const v8::Local<v8::Context>& context,
const v8::Local<v8::Object>& js_config,
const std::string_view& key,
T& value_ref,
const std::array<std::pair<std::string_view, T>, kValueSize>& values
) {
auto maybe_value = js_config->Get(context, Nan::LocalStringUTF8(key));
if(maybe_value.IsEmpty() || maybe_value.ToLocalChecked()->IsNullOrUndefined()) {
return true;
} else if(!maybe_value.ToLocalChecked()->IsString()) {
Nan::ThrowError(Nan::LocalStringUTF8("property " + std::string{key} + " isn't a string"));
return false;
}
auto str_value = maybe_value.ToLocalChecked()->ToString(context).ToLocalChecked();
auto value = *Nan::Utf8String(str_value);
for(const auto& [ key, key_value ] : values) {
if(key != value) {
continue;
}
value_ref = key_value;
return true;
}
Nan::ThrowError(Nan::LocalStringUTF8("property " + std::string{key} + " contains an invalid enum value (" + value + ")"));
return false;
}
#define LOAD_ENUM(path, arg_count, ...) \
do { \
if(!load_config_enum<arg_count>(context, js_config, #path, config.path, {{ __VA_ARGS__ }})) { \
return; \
} \
} while(0)
NAN_METHOD(AudioProcessorWrapper::apply_config) {
auto handle = Nan::ObjectWrap::Unwrap<AudioProcessorWrapper>(info.Holder());
auto processor = handle->weak_processor.lock();
if (!processor) {
Nan::ThrowError("processor passed away");
return;
}
if(info.Length() != 1 || !info[0]->IsObject()) {
Nan::ThrowError("Invalid arguments");
return;
}
auto config = processor->get_config();
auto context = info.GetIsolate()->GetCurrentContext();
auto js_config = info[0]->ToObject(info.GetIsolate()->GetCurrentContext()).ToLocalChecked();
using GainControllerMode = webrtc::AudioProcessing::Config::GainController1::Mode;
using GainControllerLevelEstimator = webrtc::AudioProcessing::Config::GainController2::LevelEstimator;
using NoiseSuppressionLevel = webrtc::AudioProcessing::Config::NoiseSuppression::Level;
LOAD_CONFIG(pipeline.maximum_internal_processing_rate);
LOAD_CONFIG(pipeline.multi_channel_render);
LOAD_CONFIG(pipeline.multi_channel_capture);
LOAD_CONFIG(pre_amplifier.enabled);
LOAD_CONFIG(pre_amplifier.fixed_gain_factor);
LOAD_CONFIG(high_pass_filter.enabled);
LOAD_CONFIG(high_pass_filter.apply_in_full_band);
LOAD_CONFIG(echo_canceller.enabled);
LOAD_CONFIG(echo_canceller.mobile_mode);
LOAD_CONFIG(echo_canceller.export_linear_aec_output); /* TODO: Consider removing? */
LOAD_CONFIG(echo_canceller.enforce_high_pass_filtering);
LOAD_CONFIG(noise_suppression.enabled);
LOAD_ENUM(noise_suppression.level, 4,
{ "low", NoiseSuppressionLevel::kLow },
{ "moderate", NoiseSuppressionLevel::kModerate },
{ "high", NoiseSuppressionLevel::kHigh },
{ "very-high", NoiseSuppressionLevel::kVeryHigh }
);
LOAD_CONFIG(noise_suppression.analyze_linear_aec_output_when_available);
LOAD_CONFIG(transient_suppression.enabled);
LOAD_CONFIG(voice_detection.enabled);
LOAD_CONFIG(gain_controller1.enabled);
LOAD_ENUM(gain_controller1.mode, 3,
{ "adaptive-analog", GainControllerMode::kAdaptiveAnalog },
{ "adaptive-digital", GainControllerMode::kAdaptiveDigital },
{ "fixed-digital", GainControllerMode::kFixedDigital }
);
LOAD_CONFIG(gain_controller1.target_level_dbfs);
LOAD_CONFIG(gain_controller1.compression_gain_db);
LOAD_CONFIG(gain_controller1.enable_limiter);
LOAD_CONFIG(gain_controller1.analog_level_minimum);
LOAD_CONFIG(gain_controller1.analog_level_maximum);
LOAD_CONFIG(gain_controller1.analog_gain_controller.enabled);
LOAD_CONFIG(gain_controller1.analog_gain_controller.startup_min_volume);
LOAD_CONFIG(gain_controller1.analog_gain_controller.clipped_level_min);
LOAD_CONFIG(gain_controller1.analog_gain_controller.enable_agc2_level_estimator);
LOAD_CONFIG(gain_controller1.analog_gain_controller.enable_digital_adaptive);
LOAD_CONFIG(gain_controller2.enabled);
LOAD_CONFIG(gain_controller2.fixed_digital.gain_db);
LOAD_CONFIG(gain_controller2.adaptive_digital.enabled);
LOAD_ENUM(gain_controller2.adaptive_digital.level_estimator, 2,
{ "peak", GainControllerLevelEstimator::kPeak },
{ "rms", GainControllerLevelEstimator::kRms }
);
LOAD_CONFIG(gain_controller2.adaptive_digital.vad_probability_attack);
LOAD_CONFIG(gain_controller2.adaptive_digital.level_estimator_adjacent_speech_frames_threshold);
LOAD_CONFIG(gain_controller2.adaptive_digital.use_saturation_protector);
LOAD_CONFIG(gain_controller2.adaptive_digital.initial_saturation_margin_db);
LOAD_CONFIG(gain_controller2.adaptive_digital.extra_saturation_margin_db);
LOAD_CONFIG(gain_controller2.adaptive_digital.gain_applier_adjacent_speech_frames_threshold);
LOAD_CONFIG(gain_controller2.adaptive_digital.max_gain_change_db_per_second);
LOAD_CONFIG(gain_controller2.adaptive_digital.max_output_noise_level_dbfs);
LOAD_CONFIG(residual_echo_detector.enabled);
LOAD_CONFIG(level_estimation.enabled);
LOAD_CONFIG(rnnoise.enabled);
LOAD_CONFIG(artificial_stream_delay);
processor->apply_config(config);
}
#define PUT_STATISTIC(path) \
do { \
if(config.path.has_value()) { \
PUT_VALUE(path, Nan::New(*config.path)); \
} else { \
PUT_VALUE(path, Nan::Undefined()); \
} \
} while(0)
NAN_METHOD(AudioProcessorWrapper::get_statistics) {
auto handle = Nan::ObjectWrap::Unwrap<AudioProcessorWrapper>(info.Holder());
auto processor = handle->weak_processor.lock();
if(!processor) {
Nan::ThrowError("processor passed away");
return;
}
auto config = processor->get_statistics();
auto result = Nan::New<v8::Object>();
auto context = info.GetIsolate()->GetCurrentContext();
PUT_STATISTIC(output_rms_dbfs);
PUT_STATISTIC(voice_detected);
PUT_STATISTIC(echo_return_loss);
PUT_STATISTIC(echo_return_loss_enhancement);
PUT_STATISTIC(divergent_filter_fraction);
PUT_STATISTIC(delay_median_ms);
PUT_STATISTIC(delay_standard_deviation_ms);
PUT_STATISTIC(residual_echo_likelihood);
PUT_STATISTIC(residual_echo_likelihood_recent_max);
PUT_STATISTIC(delay_ms);
PUT_STATISTIC(rnnoise_volume);
info.GetReturnValue().Set(result);
}
void AudioProcessorWrapper::Observer::stream_processed(const AudioProcessor::Stats &stats) {
/* TODO! */
}