This commit is contained in:
Steven Dan
2025-12-11 09:43:42 +08:00
commit d8b2974133
1822 changed files with 280037 additions and 0 deletions

View 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

View 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

View 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()

View 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

View 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

View 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);
}