init
This commit is contained in:
235
lib_agc/lib_agc/api/agc_api.h
Normal file
235
lib_agc/lib_agc/api/agc_api.h
Normal file
@@ -0,0 +1,235 @@
|
||||
// Copyright 2022 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
#ifndef AGC_API_H
|
||||
#define AGC_API_H
|
||||
|
||||
#include "xmath/xmath.h"
|
||||
#include <agc_profiles.h>
|
||||
|
||||
/**
|
||||
* @page page_agc_api_h agc_api.h
|
||||
*
|
||||
* This header should be included in application source code to gain access to the
|
||||
* lib_agc public functions API.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup agc_func AGC API functions
|
||||
* @defgroup agc_defs AGC API structure definitions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Length of the frame of data on which the AGC will operate.
|
||||
*
|
||||
* @ingroup agc_defs
|
||||
*/
|
||||
#define appconfAUDIO_PIPELINE_FRAME_ADVANCE 512
|
||||
#define AGC_FRAME_ADVANCE 512u
|
||||
|
||||
/**
|
||||
* @brief AGC configuration structure
|
||||
*
|
||||
* This structure contains configuration settings that can be changed to alter the
|
||||
* behaviour of the AGC instance.
|
||||
*
|
||||
* Members with the "lc_" prefix are parameters for the Loss Control feature.
|
||||
*
|
||||
* @ingroup agc_defs
|
||||
*/
|
||||
typedef struct {
|
||||
/** Boolean to enable AGC adaption; if enabled, the gain to apply will adapt based on the
|
||||
* peak of the input frame and the upper/lower threshold parameters. */
|
||||
int adapt;
|
||||
/** Boolean to enable adaption based on the VNR meta-data; if enabled, adaption will always
|
||||
* be performed when voice activity is detected. This must be disabled if the application
|
||||
* doesn't have a VNR. */
|
||||
int adapt_on_vnr;
|
||||
/** Boolean to enable soft-clipping of the output frame. */
|
||||
int soft_clipping;
|
||||
/** The current gain to be applied, not including loss control. */
|
||||
float_s32_t gain;
|
||||
/** The maximum gain allowed when adaption is enabled. */
|
||||
float_s32_t max_gain;
|
||||
/** The minimum gain allowed when adaption is enabled. */
|
||||
float_s32_t min_gain;
|
||||
/** The upper limit for the gained peak of the frame when adaption is enabled. */
|
||||
float_s32_t upper_threshold;
|
||||
/** The lower limit for the gained peak of the frame when adaption is enabled. */
|
||||
float_s32_t lower_threshold;
|
||||
/** Factor by which to increase the gain during adaption. */
|
||||
float_s32_t gain_inc;
|
||||
/** Factor by which to decrease the gain during adaption. */
|
||||
float_s32_t gain_dec;
|
||||
/** Boolean to enable loss control. This must be disabled if the application doesn't have
|
||||
* an AEC. */
|
||||
int lc_enabled;
|
||||
/** Number of frames required to consider far-end audio active. */
|
||||
int lc_n_frame_far;
|
||||
/** Number of frames required to consider near-end audio active. */
|
||||
int lc_n_frame_near;
|
||||
/** Threshold for far-end correlation above which to indicate far-end activity only. */
|
||||
float_s32_t lc_corr_threshold;
|
||||
/** Gamma coefficient for estimating the power of the far-end background noise. */
|
||||
float_s32_t lc_bg_power_gamma;
|
||||
/** Factor by which to increase the loss control gain when less than target value. */
|
||||
float_s32_t lc_gamma_inc;
|
||||
/** Factor by which to decrease the loss control gain when greater than target value. */
|
||||
float_s32_t lc_gamma_dec;
|
||||
/** Delta multiplier used when only far-end activity is detected. */
|
||||
float_s32_t lc_far_delta;
|
||||
/** Delta multiplier used when only near-end activity is detected. */
|
||||
float_s32_t lc_near_delta;
|
||||
/** Delta multiplier used when both near-end and far-end activity is detected. */
|
||||
float_s32_t lc_near_delta_far_active;
|
||||
/** Loss control gain to apply when near-end activity only is detected. */
|
||||
float_s32_t lc_gain_max;
|
||||
/** Loss control gain to apply when double-talk is detected. */
|
||||
float_s32_t lc_gain_double_talk;
|
||||
/** Loss control gain to apply when silence is detected. */
|
||||
float_s32_t lc_gain_silence;
|
||||
/** Loss control gain to apply when far-end activity only is detected. */
|
||||
float_s32_t lc_gain_min;
|
||||
} agc_config_t;
|
||||
|
||||
/**
|
||||
* @brief AGC state structure
|
||||
*
|
||||
* This structure holds the current state of the AGC instance and members are updated each
|
||||
* time that `agc_process_frame()` runs. Many of these members are exponentially-weighted
|
||||
* moving averages (EWMA) which influence the adaption of the AGC gain or the loss control
|
||||
* feature. The user should not directly modify any of these members, except the config.
|
||||
*
|
||||
* @ingroup agc_defs
|
||||
*/
|
||||
typedef struct {
|
||||
/** The current configuration of the AGC. Any member of this configuration structure can
|
||||
* be modified and that change will take effect on the next run of `agc_process_frame()`. */
|
||||
agc_config_t config;
|
||||
/** EWMA of the frame peak, which is used to identify the overall trend of a rise or fall
|
||||
* in the input signal. */
|
||||
float_s32_t x_slow;
|
||||
/** EWMA of the frame peak, which is used to identify a rise or fall in the peak of frame. */
|
||||
float_s32_t x_fast;
|
||||
/** EWMA of `x_fast`, which is used when adapting to the `agc_config_t::upper_threshold`. */
|
||||
float_s32_t x_peak;
|
||||
/** Timer counting down until enough frames with far-end activity have been processed. */
|
||||
int lc_t_far;
|
||||
/** Timer counting down until enough frames with near-end activity have been processed. */
|
||||
int lc_t_near;
|
||||
/** EWMA of estimates of the near-end power. */
|
||||
float_s32_t lc_near_power_est;
|
||||
/** EWMA of estimates of the far-end power. */
|
||||
float_s32_t lc_far_power_est;
|
||||
/** EWMA of estimates of the power of near-end background noise. */
|
||||
float_s32_t lc_near_bg_power_est;
|
||||
/** Loss control gain applied on top of the AGC gain in `agc_config_t`. */
|
||||
float_s32_t lc_gain;
|
||||
/** EWMA of estimates of the power of far-end background noise. */
|
||||
float_s32_t lc_far_bg_power_est;
|
||||
/** EWMA of the far-end correlation for detecting double-talk. */
|
||||
float_s32_t lc_corr_val;
|
||||
} agc_state_t;
|
||||
|
||||
/**
|
||||
* @brief Initialise the AGC
|
||||
*
|
||||
* This function initialises the AGC state with the provided configuration. It must be called
|
||||
* at startup to initialise the AGC before processing any frames, and can be called at any time
|
||||
* after that to reset the AGC instance, returning the internal AGC state to its defaults.
|
||||
*
|
||||
* @param[out] agc AGC state structure
|
||||
* @param[in] config Initial configuration values
|
||||
*
|
||||
* @par Example with an unmodified profile
|
||||
* @code{.c}
|
||||
* agc_state_t agc;
|
||||
agc_init(&agc, &AGC_PROFILE_ASR);
|
||||
* @endcode
|
||||
*
|
||||
* @par Example with modification to the profile
|
||||
* @code{.c}
|
||||
* agc_config_t conf = AGC_PROFILE_FIXED_GAIN;
|
||||
conf.gain = f32_to_float_s32(100);
|
||||
agc_state_t agc;
|
||||
agc_init(&agc, &conf);
|
||||
* @endcode
|
||||
*
|
||||
* @ingroup agc_func
|
||||
*/
|
||||
void agc_init(agc_state_t *agc, agc_config_t *config);
|
||||
|
||||
/**
|
||||
* @brief AGC meta data structure
|
||||
*
|
||||
* This structure holds meta-data about the current frame to be processed, and must be updated
|
||||
* to reflect the current frame before calling `agc_process_frame()`.
|
||||
*
|
||||
* @ingroup agc_defs
|
||||
*/
|
||||
typedef struct {
|
||||
/** Boolean to indicate the detection of voice activity in the current frame. */
|
||||
int vnr_flag;
|
||||
/** The power of the most powerful reference channel. */
|
||||
float_s32_t aec_ref_power;
|
||||
/** Correlation factor between the microphone input and the AEC's estimated microphone
|
||||
* signal. */
|
||||
float_s32_t aec_corr_factor;
|
||||
} agc_meta_data_t;
|
||||
|
||||
/**
|
||||
* If the application has no VNR, `adapt_on_vnr` must be disabled in the configuration. This
|
||||
* pre-processor definition can be assigned to the `vnr_flag` in `agc_meta_data_t` in that
|
||||
* situation to make it clear in the code that there is no VNR.
|
||||
*
|
||||
* @ingroup agc_defs
|
||||
*/
|
||||
#define AGC_META_DATA_NO_VNR 0u
|
||||
|
||||
/**
|
||||
* If the application has VNR, `adapt_on_vnr` can be enabled in the configuration. This
|
||||
* define is used to covert VNR value from uint8_t to boolean.
|
||||
*
|
||||
*/
|
||||
#define AGC_VNR_THRESHOLD 205
|
||||
|
||||
/**
|
||||
* If the application has no AEC, `lc_enabled` must be disabled in the configuration. This
|
||||
* pre-processor definition can be assigned to the `aec_ref_power` and `aec_corr_factor` in
|
||||
* `agc_meta_data_t` in that situation to make it clear in the code that there is no AEC.
|
||||
*
|
||||
* @ingroup agc_defs
|
||||
*/
|
||||
#define AGC_META_DATA_NO_AEC (float_s32_t){0, 0}
|
||||
|
||||
/**
|
||||
* @brief Perform AGC processing on a frame of input data
|
||||
*
|
||||
* This function updates the AGC's internal state based on the input frame and meta-data, and
|
||||
* returns an output containing the result of the AGC algorithm applied to the input.
|
||||
*
|
||||
* The `input` and `output` pointers can be equal to perform the processing in-place.
|
||||
*
|
||||
* @param[inout] agc AGC state structure
|
||||
* @param[out] output Array to return the resulting frame of data
|
||||
* @param[in] input Array of frame data on which to perform the AGC
|
||||
* @param[in] meta_data Meta-data structure with VNR/AEC data
|
||||
*
|
||||
* @par Example
|
||||
* @code{.c}
|
||||
* int32_t input[AGC_FRAME_ADVANCE];
|
||||
int32_t output[AGC_FRAME_ADVANCE];
|
||||
agc_meta_data md;
|
||||
md.vnr_flag = AGC_META_DATA_NO_VNR;
|
||||
md.aec_ref_power = AGC_META_DATA_NO_AEC;
|
||||
md.aec_corr_factor = AGC_META_DATA_NO_AEC;
|
||||
agc_process_frame(&agc, output, input, &md);
|
||||
* @endcode
|
||||
*
|
||||
* @ingroup agc_func
|
||||
*/
|
||||
void agc_process_frame(agc_state_t *agc,
|
||||
int32_t output[AGC_FRAME_ADVANCE],
|
||||
const int32_t input[AGC_FRAME_ADVANCE],
|
||||
agc_meta_data_t *meta_data);
|
||||
|
||||
#endif
|
||||
116
lib_agc/lib_agc/api/agc_profiles.h
Normal file
116
lib_agc/lib_agc/api/agc_profiles.h
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright 2022 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
#ifndef AGC_PROFILES_H
|
||||
#define AGC_PROFILES_H
|
||||
|
||||
#include "xmath/xmath.h"
|
||||
|
||||
/**
|
||||
* @page page_agc_profiles_h agc_profiles.h
|
||||
*
|
||||
* This header contains pre-defined profiles for AGC configurations.
|
||||
* These profiles can be used to initialise the `agc_config_t` data
|
||||
* for use with `agc_init()`.
|
||||
*
|
||||
* This header is automatically included by `agc_api.h`.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup agc_profiles Pre-defined AGC configuration profiles
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief AGC profile tuned for Automatic Speech Recognition (ASR).
|
||||
*
|
||||
* @ingroup agc_profiles
|
||||
*/
|
||||
|
||||
#define AGC_PROFILE_ASR_HIGH_GAIN (agc_config_t){ \
|
||||
.adapt = 1, \
|
||||
.adapt_on_vnr = 1, \
|
||||
.soft_clipping = 1, \
|
||||
.gain = f32_to_float_s32(10000), \
|
||||
.max_gain = f32_to_float_s32(10000), \
|
||||
.min_gain = f32_to_float_s32(0), \
|
||||
.upper_threshold = f32_to_float_s32(10000), \
|
||||
.lower_threshold = f32_to_float_s32(0.7000), \
|
||||
.gain_inc = f32_to_float_s32(1.197), \
|
||||
.gain_dec = f32_to_float_s32(0.87), \
|
||||
.lc_enabled = 0, \
|
||||
.lc_n_frame_far = 0, \
|
||||
.lc_n_frame_near = 0, \
|
||||
.lc_corr_threshold = f32_to_float_s32(0), \
|
||||
.lc_bg_power_gamma = f32_to_float_s32(0), \
|
||||
.lc_gamma_inc = f32_to_float_s32(0), \
|
||||
.lc_gamma_dec = f32_to_float_s32(0), \
|
||||
.lc_far_delta = f32_to_float_s32(0), \
|
||||
.lc_near_delta = f32_to_float_s32(0), \
|
||||
.lc_near_delta_far_active = f32_to_float_s32(0), \
|
||||
.lc_gain_max = f32_to_float_s32(0), \
|
||||
.lc_gain_double_talk = f32_to_float_s32(0), \
|
||||
.lc_gain_silence = f32_to_float_s32(0), \
|
||||
.lc_gain_min = f32_to_float_s32(0), \
|
||||
}
|
||||
|
||||
|
||||
#define AGC_PROFILE_ASR (agc_config_t){ \
|
||||
.adapt = 1, \
|
||||
.adapt_on_vnr = 0, \
|
||||
.soft_clipping = 0, \
|
||||
.gain = f32_to_float_s32(500), \
|
||||
.max_gain = f32_to_float_s32(1000), \
|
||||
.min_gain = f32_to_float_s32(0), \
|
||||
.upper_threshold = f32_to_float_s32(0.9999), \
|
||||
.lower_threshold = f32_to_float_s32(0.7000), \
|
||||
.gain_inc = f32_to_float_s32(1.197), \
|
||||
.gain_dec = f32_to_float_s32(0.87), \
|
||||
.lc_enabled = 0, \
|
||||
.lc_n_frame_far = 0, \
|
||||
.lc_n_frame_near = 0, \
|
||||
.lc_corr_threshold = f32_to_float_s32(0), \
|
||||
.lc_bg_power_gamma = f32_to_float_s32(0), \
|
||||
.lc_gamma_inc = f32_to_float_s32(0), \
|
||||
.lc_gamma_dec = f32_to_float_s32(0), \
|
||||
.lc_far_delta = f32_to_float_s32(0), \
|
||||
.lc_near_delta = f32_to_float_s32(0), \
|
||||
.lc_near_delta_far_active = f32_to_float_s32(0), \
|
||||
.lc_gain_max = f32_to_float_s32(0), \
|
||||
.lc_gain_double_talk = f32_to_float_s32(0), \
|
||||
.lc_gain_silence = f32_to_float_s32(0), \
|
||||
.lc_gain_min = f32_to_float_s32(0), \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief AGC profile tuned to apply a fixed gain.
|
||||
*
|
||||
* @ingroup agc_profiles
|
||||
*/
|
||||
#define AGC_PROFILE_FIXED_GAIN (agc_config_t){ \
|
||||
.adapt = 0, \
|
||||
.adapt_on_vnr = 0, \
|
||||
.soft_clipping = 0, \
|
||||
.gain = f32_to_float_s32(25), \
|
||||
.max_gain = f32_to_float_s32(0), \
|
||||
.min_gain = f32_to_float_s32(0), \
|
||||
.upper_threshold = f32_to_float_s32(0), \
|
||||
.lower_threshold = f32_to_float_s32(0), \
|
||||
.gain_inc = f32_to_float_s32(0), \
|
||||
.gain_dec = f32_to_float_s32(0), \
|
||||
.lc_enabled = 0, \
|
||||
.lc_n_frame_far = 0, \
|
||||
.lc_n_frame_near = 0, \
|
||||
.lc_corr_threshold = f32_to_float_s32(0), \
|
||||
.lc_bg_power_gamma = f32_to_float_s32(0), \
|
||||
.lc_gamma_inc = f32_to_float_s32(0), \
|
||||
.lc_gamma_dec = f32_to_float_s32(0), \
|
||||
.lc_far_delta = f32_to_float_s32(0), \
|
||||
.lc_near_delta = f32_to_float_s32(0), \
|
||||
.lc_near_delta_far_active = f32_to_float_s32(0), \
|
||||
.lc_gain_max = f32_to_float_s32(0), \
|
||||
.lc_gain_double_talk = f32_to_float_s32(0), \
|
||||
.lc_gain_silence = f32_to_float_s32(0), \
|
||||
.lc_gain_min = f32_to_float_s32(0), \
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
6
lib_agc/lib_agc/lib_build_info.cmake
Normal file
6
lib_agc/lib_agc/lib_build_info.cmake
Normal file
@@ -0,0 +1,6 @@
|
||||
set(LIB_NAME lib_agc)
|
||||
set(LIB_VERSION 1.0.0)
|
||||
set(LIB_INCLUDES api)
|
||||
set(LIB_DEPENDENT_MODULES "")
|
||||
|
||||
XMOS_REGISTER_MODULE()
|
||||
12
lib_agc/lib_agc/module_build_info
Normal file
12
lib_agc/lib_agc/module_build_info
Normal file
@@ -0,0 +1,12 @@
|
||||
# You can set flags specifically for your module by using the MODULE_XCC_FLAGS
|
||||
# variable. So the following
|
||||
#
|
||||
# MODULE_XCC_FLAGS = $(XCC_FLAGS) -O3
|
||||
#
|
||||
# specifies that everything in the modules should have the application
|
||||
# build flags with -O3 appended (so the files will build at
|
||||
# optimization level -O3).
|
||||
#
|
||||
# You can also set MODULE_XCC_C_FLAGS, MODULE_XCC_XC_FLAGS etc..
|
||||
|
||||
VERSION = 1.0.0
|
||||
35
lib_agc/lib_agc/src/agc_defines.h
Normal file
35
lib_agc/lib_agc/src/agc_defines.h
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2022 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
#ifndef AGC_DEFINES_H
|
||||
#define AGC_DEFINES_H
|
||||
|
||||
#include "xmath/xmath.h"
|
||||
|
||||
|
||||
// The input and output frame data format is Q1.31
|
||||
#define FRAME_EXP -31
|
||||
|
||||
// Pre-calculated values to avoid the cycles of f32_to_float_s32()
|
||||
#define FLOAT_S32_ZERO (float_s32_t){0, -31}
|
||||
#define FLOAT_S32_ONE (float_s32_t){1073741824, -30}
|
||||
|
||||
// Alphas for EMA calculations are in Q30 format for float_s32_ema()
|
||||
#define AGC_ALPHA_SLOW_RISE 952301632 // 0.8869
|
||||
#define AGC_ALPHA_SLOW_FALL 1035731392 // 0.9646
|
||||
#define AGC_ALPHA_FAST_RISE 409525120 // 0.3814
|
||||
#define AGC_ALPHA_FAST_FALL 952301632 // 0.8869
|
||||
#define AGC_ALPHA_PEAK_RISE 588410496 // 0.5480
|
||||
#define AGC_ALPHA_PEAK_FALL 1035731392 // 0.9646
|
||||
#define AGC_ALPHA_LC_EST_INC 588410496 // 0.5480
|
||||
#define AGC_ALPHA_LC_EST_DEC 748720192 // 0.6973
|
||||
#define AGC_ALPHA_LC_BG_POWER_EST_DEC 588410496 // 0.5480
|
||||
#define AGC_ALPHA_LC_CORR 1052267008 // 0.9800
|
||||
|
||||
// Minimum value for the estimated far background power
|
||||
#define AGC_LC_FAR_BG_POWER_EST_MIN (float_s32_t){1407374848, -47} //0.00001
|
||||
|
||||
// Pre-calculated values for soft-clipping constants
|
||||
#define AGC_SOFT_CLIPPING_THRESH (float_s32_t){1073741824, -31} // 0.5
|
||||
#define AGC_SOFT_CLIPPING_NUMERATOR (float_s32_t){1073741824, -32} // 0.25; AGC_SOFT_CLIPPING_THRESH squared
|
||||
|
||||
#endif
|
||||
258
lib_agc/lib_agc/src/agc_impl.c
Normal file
258
lib_agc/lib_agc/src/agc_impl.c
Normal file
@@ -0,0 +1,258 @@
|
||||
// Copyright 2022 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
#include <limits.h>
|
||||
#include "agc_defines.h"
|
||||
#include "xmath/xmath.h"
|
||||
#include <agc_api.h>
|
||||
|
||||
void agc_init(agc_state_t *agc, agc_config_t *config)
|
||||
{
|
||||
agc->config = *config;
|
||||
|
||||
agc->x_slow = f32_to_float_s32(0);
|
||||
agc->x_fast = f32_to_float_s32(0);
|
||||
agc->x_peak = f32_to_float_s32(0);
|
||||
|
||||
agc->lc_t_far = 0;
|
||||
agc->lc_t_near = 0;
|
||||
|
||||
agc->lc_near_power_est = f32_to_float_s32(0.00001F);
|
||||
agc->lc_far_power_est = f32_to_float_s32(0.01F);
|
||||
agc->lc_near_bg_power_est = f32_to_float_s32(0.01F);
|
||||
agc->lc_gain = f32_to_float_s32(1);
|
||||
agc->lc_far_bg_power_est = f32_to_float_s32(0.01F);
|
||||
agc->lc_corr_val = f32_to_float_s32(0);
|
||||
}
|
||||
|
||||
// Returns the mantissa for the input float shifted to an exponent of parameter exp
|
||||
static int32_t use_exp_float(float_s32_t fl, exponent_t exp)
|
||||
{
|
||||
exponent_t exp_diff = fl.exp - exp;
|
||||
|
||||
if (exp_diff > 0) {
|
||||
return fl.mant << exp_diff;
|
||||
} else if (exp_diff < 0) {
|
||||
return fl.mant >> -exp_diff;
|
||||
}
|
||||
|
||||
return fl.mant;
|
||||
}
|
||||
|
||||
// Returns the soft-clipped mantissa in terms of the original exponent
|
||||
static int32_t apply_soft_clipping(int32_t mant, exponent_t exp)
|
||||
{
|
||||
float_s32_t sample = {mant, exp};
|
||||
float_s32_t sample_abs = float_s32_abs(sample);
|
||||
|
||||
if (float_s32_gt(AGC_SOFT_CLIPPING_THRESH, sample_abs)) {
|
||||
return mant;
|
||||
}
|
||||
|
||||
// Division by zero is not possible after the absolute value test against AGC_LC_LIMIT_POINT
|
||||
float_s32_t sample_limit = float_s32_div(AGC_SOFT_CLIPPING_NUMERATOR, sample_abs);
|
||||
sample_limit = float_s32_sub(FLOAT_S32_ONE, sample_limit);
|
||||
|
||||
if (float_s32_gt(FLOAT_S32_ZERO, sample)) {
|
||||
sample_limit = float_s32_sub(FLOAT_S32_ZERO, sample_limit);
|
||||
}
|
||||
|
||||
return use_exp_float(sample_limit, exp);
|
||||
}
|
||||
|
||||
void agc_process_frame(agc_state_t *agc,
|
||||
int32_t output[AGC_FRAME_ADVANCE],
|
||||
const int32_t input[AGC_FRAME_ADVANCE],
|
||||
agc_meta_data_t *meta_data)
|
||||
{
|
||||
int vnr_flag = meta_data->vnr_flag;
|
||||
|
||||
if (agc->config.adapt_on_vnr == 0) {
|
||||
vnr_flag = 1;
|
||||
}
|
||||
|
||||
bfp_s32_t input_bfp;
|
||||
bfp_s32_init(&input_bfp, (int32_t *)input, FRAME_EXP, AGC_FRAME_ADVANCE, 1);
|
||||
|
||||
bfp_s32_t output_bfp;
|
||||
bfp_s32_init(&output_bfp, (int32_t *)output, FRAME_EXP, AGC_FRAME_ADVANCE, 0);
|
||||
|
||||
if (agc->config.adapt) {
|
||||
// Get max absolute sample value by comparing the absolute values of the min and max.
|
||||
// An alternative approach is to form a new vector with the absolute values and then find
|
||||
// the max value, which took 48 fewer cycles but required an extra 760 bytes of memory.
|
||||
float_s32_t max_sample = float_s32_abs(bfp_s32_max(&input_bfp));
|
||||
float_s32_t min_sample = float_s32_abs(bfp_s32_min(&input_bfp));
|
||||
float_s32_t max_abs_value;
|
||||
|
||||
if (float_s32_gte(max_sample, min_sample)) {
|
||||
max_abs_value = max_sample;
|
||||
} else {
|
||||
max_abs_value = min_sample;
|
||||
}
|
||||
|
||||
unsigned rising = float_s32_gte(max_abs_value, agc->x_slow);
|
||||
if (rising) {
|
||||
agc->x_slow = float_s32_ema(agc->x_slow, max_abs_value, AGC_ALPHA_SLOW_RISE);
|
||||
agc->x_fast = float_s32_ema(agc->x_fast, max_abs_value, AGC_ALPHA_FAST_RISE);
|
||||
} else {
|
||||
agc->x_slow = float_s32_ema(agc->x_slow, max_abs_value, AGC_ALPHA_SLOW_FALL);
|
||||
agc->x_fast = float_s32_ema(agc->x_fast, max_abs_value, AGC_ALPHA_FAST_FALL);
|
||||
}
|
||||
|
||||
float_s32_t gained_max_abs_value = float_s32_mul(max_abs_value, agc->config.gain);
|
||||
unsigned exceed_threshold = float_s32_gte(gained_max_abs_value, agc->config.upper_threshold);
|
||||
|
||||
if (exceed_threshold || vnr_flag) {
|
||||
unsigned peak_rising = float_s32_gte(agc->x_fast, agc->x_peak);
|
||||
if (peak_rising) {
|
||||
agc->x_peak = float_s32_ema(agc->x_peak, agc->x_fast, AGC_ALPHA_PEAK_RISE);
|
||||
} else {
|
||||
agc->x_peak = float_s32_ema(agc->x_peak, agc->x_fast, AGC_ALPHA_PEAK_FALL);
|
||||
}
|
||||
|
||||
float_s32_t gained_pk = float_s32_mul(agc->x_peak, agc->config.gain);
|
||||
unsigned near_only = (agc->lc_t_near != 0) && (agc->lc_t_far == 0);
|
||||
if (float_s32_gte(gained_pk, agc->config.upper_threshold)) {
|
||||
agc->config.gain = float_s32_mul(agc->config.gain_dec, agc->config.gain);
|
||||
} else if (float_s32_gte(agc->config.lower_threshold, gained_pk) &&
|
||||
(agc->config.lc_enabled == 0 || near_only != 0)) {
|
||||
agc->config.gain = float_s32_mul(agc->config.gain_inc, agc->config.gain);
|
||||
}
|
||||
|
||||
if (float_s32_gte(agc->config.gain, agc->config.max_gain)) {
|
||||
agc->config.gain = agc->config.max_gain;
|
||||
}
|
||||
|
||||
if (float_s32_gte(agc->config.min_gain, agc->config.gain)) {
|
||||
agc->config.gain = agc->config.min_gain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float_s32_t frame_power = float_s64_to_float_s32(bfp_s32_energy(&input_bfp));
|
||||
bfp_s32_scale(&output_bfp, &input_bfp, agc->config.gain);
|
||||
|
||||
// Update loss control state
|
||||
|
||||
if (float_s32_gte(agc->lc_far_power_est, meta_data->aec_ref_power)) {
|
||||
agc->lc_far_power_est = float_s32_ema(agc->lc_far_power_est, meta_data->aec_ref_power, AGC_ALPHA_LC_EST_DEC);
|
||||
} else {
|
||||
agc->lc_far_power_est = float_s32_ema(agc->lc_far_power_est, meta_data->aec_ref_power, AGC_ALPHA_LC_EST_INC);
|
||||
}
|
||||
|
||||
float_s32_t far_bg_power_est = float_s32_mul(agc->config.lc_bg_power_gamma, agc->lc_far_bg_power_est);
|
||||
if (float_s32_gte(far_bg_power_est, agc->lc_far_power_est)) {
|
||||
agc->lc_far_bg_power_est = agc->lc_far_power_est;
|
||||
} else {
|
||||
agc->lc_far_bg_power_est = far_bg_power_est;
|
||||
}
|
||||
|
||||
if (float_s32_gte(AGC_LC_FAR_BG_POWER_EST_MIN, agc->lc_far_bg_power_est)) {
|
||||
agc->lc_far_bg_power_est = AGC_LC_FAR_BG_POWER_EST_MIN;
|
||||
}
|
||||
|
||||
if (float_s32_gte(agc->lc_near_power_est, frame_power)) {
|
||||
agc->lc_near_power_est = float_s32_ema(agc->lc_near_power_est, frame_power, AGC_ALPHA_LC_EST_DEC);
|
||||
} else {
|
||||
agc->lc_near_power_est = float_s32_ema(agc->lc_near_power_est, frame_power, AGC_ALPHA_LC_EST_INC);
|
||||
}
|
||||
|
||||
if (float_s32_gt(agc->lc_near_bg_power_est, agc->lc_near_power_est)) {
|
||||
agc->lc_near_bg_power_est = float_s32_ema(agc->lc_near_bg_power_est, agc->lc_near_power_est, AGC_ALPHA_LC_BG_POWER_EST_DEC);
|
||||
} else {
|
||||
agc->lc_near_bg_power_est = float_s32_mul(agc->config.lc_bg_power_gamma, agc->lc_near_bg_power_est);
|
||||
}
|
||||
|
||||
if (agc->config.lc_enabled) {
|
||||
if (float_s32_gt(meta_data->aec_corr_factor, agc->lc_corr_val)) {
|
||||
agc->lc_corr_val = meta_data->aec_corr_factor;
|
||||
} else {
|
||||
agc->lc_corr_val = float_s32_ema(agc->lc_corr_val, meta_data->aec_corr_factor, AGC_ALPHA_LC_CORR);
|
||||
}
|
||||
|
||||
if (float_s32_gt(agc->lc_far_power_est, float_s32_mul(agc->config.lc_far_delta, agc->lc_far_bg_power_est))) {
|
||||
agc->lc_t_far = agc->config.lc_n_frame_far;
|
||||
} else {
|
||||
if (agc->lc_t_far > 0) {
|
||||
--agc->lc_t_far;
|
||||
}
|
||||
}
|
||||
|
||||
float_s32_t delta = (agc->lc_t_far > 0) ? agc->config.lc_near_delta_far_active : agc->config.lc_near_delta;
|
||||
|
||||
if (float_s32_gt(agc->lc_near_power_est, float_s32_mul(delta, agc->lc_near_bg_power_est))) {
|
||||
if (agc->lc_t_far == 0 || (agc->lc_t_far > 0 &&
|
||||
float_s32_gt(agc->config.lc_corr_threshold, agc->lc_corr_val))) {
|
||||
// Near-end speech only or double talk
|
||||
agc->lc_t_near = agc->config.lc_n_frame_near;
|
||||
} else {
|
||||
// Far-end speech only
|
||||
// Do nothing
|
||||
}
|
||||
} else {
|
||||
// Silence
|
||||
if (agc->lc_t_near > 0) {
|
||||
--agc->lc_t_near;
|
||||
}
|
||||
}
|
||||
|
||||
// Adapt loss control gain
|
||||
float_s32_t lc_target_gain;
|
||||
if (agc->lc_t_far <= 0 && agc->lc_t_near > 0) {
|
||||
// Near-end only
|
||||
lc_target_gain = agc->config.lc_gain_max;
|
||||
} else if (agc->lc_t_far <= 0 && agc->lc_t_near <= 0) {
|
||||
// Silence
|
||||
lc_target_gain = agc->config.lc_gain_silence;
|
||||
} else if (agc->lc_t_far > 0 && agc->lc_t_near <= 0) {
|
||||
// Far-end only
|
||||
lc_target_gain = agc->config.lc_gain_min;
|
||||
} else {
|
||||
// Double talk
|
||||
lc_target_gain = agc->config.lc_gain_double_talk;
|
||||
}
|
||||
|
||||
// When changing from one value of lc_target_gain to a different one, the change
|
||||
// is applied gradually, sample-by-sample in the frame, using lc_gamma_inc/dec.
|
||||
// The lc_scale array is initially set to the target value and then overwritten
|
||||
// from the beginning as required to transition from the previous lc_gain value.
|
||||
// This will create a BFP array representing the gradual scale changes which
|
||||
// can be applied by multiplying element-wise using the VPU.
|
||||
int32_t lc_scale[AGC_FRAME_ADVANCE];
|
||||
bfp_s32_t lc_scale_bfp;
|
||||
bfp_s32_init(&lc_scale_bfp, lc_scale, lc_target_gain.exp, AGC_FRAME_ADVANCE, 0);
|
||||
bfp_s32_set(&lc_scale_bfp, lc_target_gain.mant, lc_target_gain.exp);
|
||||
// Add some headroom to avoid changing the exponent when gradually transitioning from
|
||||
// previous lc_gain to lc_target_gain. Anyway, 32 bits of precision is unnecessary.
|
||||
bfp_s32_shl(&lc_scale_bfp, &lc_scale_bfp, -8);
|
||||
lc_scale_bfp.exp += 8;
|
||||
|
||||
for (unsigned idx = 0; idx < AGC_FRAME_ADVANCE; ++idx) {
|
||||
if (float_s32_gt(agc->lc_gain, lc_target_gain)) {
|
||||
agc->lc_gain = float_s32_mul(agc->lc_gain, agc->config.lc_gamma_dec);
|
||||
if (float_s32_gt(lc_target_gain, agc->lc_gain)) {
|
||||
agc->lc_gain = lc_target_gain;
|
||||
}
|
||||
lc_scale[idx] = use_exp_float(agc->lc_gain, lc_target_gain.exp);
|
||||
} else if (float_s32_gt(lc_target_gain, agc->lc_gain)) {
|
||||
agc->lc_gain = float_s32_mul(agc->lc_gain, agc->config.lc_gamma_inc);
|
||||
if (float_s32_gt(agc->lc_gain, lc_target_gain)) {
|
||||
agc->lc_gain = lc_target_gain;
|
||||
}
|
||||
lc_scale[idx] = use_exp_float(agc->lc_gain, lc_target_gain.exp);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bfp_s32_mul(&output_bfp, &output_bfp, &lc_scale_bfp);
|
||||
}
|
||||
|
||||
if (agc->config.soft_clipping) {
|
||||
for (unsigned idx = 0; idx < AGC_FRAME_ADVANCE; ++idx) {
|
||||
output[idx] = apply_soft_clipping(output[idx], output_bfp.exp);
|
||||
}
|
||||
}
|
||||
|
||||
bfp_s32_use_exponent(&output_bfp, FRAME_EXP);
|
||||
}
|
||||
Reference in New Issue
Block a user