From 515f690458c3c050ab2b6faa5b3bdb9bb2c56725 Mon Sep 17 00:00:00 2001 From: Annemarie Porter Date: Thu, 3 Apr 2025 11:39:14 -0700 Subject: [PATCH] modules: Add iir_mbdrc module and drc module Add modules iir_mbdrc and drc under the gain_control folder, and the necessary dependency files. Signed-off-by: Annemarie Porter --- CMakeLists.txt | 3 + arch/linux/configs/defconfig | 2 + modules/CMakeLists.txt | 7 + modules/Kconfig | 9 + .../common/internal_api/imcl_drc_info_api.h | 72 + modules/cmn/common/utils/build/CMakeLists.txt | 1 + .../common/utils/inc/audio_apiir_df1_opt.h | 87 + modules/cmn/common/utils/src/apiir_df1_opt.c | 205 ++ .../processing/gain_control/drc/api/api_drc.h | 289 +++ .../gain_control/drc/build/CMakeLists.txt | 45 + .../gain_control/drc/capi/inc/capi_drc.h | 37 + .../gain_control/drc/capi/src/capi_drc.cpp | 622 +++++ .../drc/capi/src/capi_drc_utils.cpp | 283 +++ .../drc/capi/src/capi_drc_utils.h | 77 + .../gain_control/drc/lib/inc/CDrcLib.h | 289 +++ .../gain_control/drc/lib/inc/drc_api.h | 126 + .../gain_control/drc/lib/inc/drc_calib_api.h | 134 ++ .../gain_control/drc/lib/src/CDrcLib.cpp | 1267 ++++++++++ .../gain_control/drc/lib/src/drc_lib.c | 2079 +++++++++++++++++ .../gain_control/drc/lib/src/drc_lib.h | 191 ++ .../gain_control/drc/lib/src/drc_lib_opt.c | 1147 +++++++++ .../drc/lib/stub_src/CDrcLib_stub.cpp | 59 + .../gain_control/iir_mbdrc/api/mbdrc_api.h | 754 ++++++ .../iir_mbdrc/bin/arm/libiir_mbdrc.a | Bin 0 -> 499152 bytes .../iir_mbdrc/build/CMakeLists.txt | 40 + .../iir_mbdrc/inc/capi_iir_mbdrc.h | 43 + .../iir_mbdrc/inc/capi_iir_mbdrc_utils.h | 369 +++ .../gain_control/iir_mbdrc/inc/iir_mbdrc.h | 149 ++ .../iir_mbdrc/inc/iir_mbdrc_api.h | 108 + .../iir_mbdrc/inc/iir_mbdrc_calibration_api.h | 195 ++ .../inc/iir_mbdrc_function_defines.h | 16 + .../gain_control/limiter/api/api_limiter.h | 160 ++ .../gain_control/limiter/build/CMakeLists.txt | 21 + .../limiter/lib/build/CMakeLists.txt | 25 + .../limiter/lib/inc/limiter24_api.h | 250 ++ .../limiter/lib/inc/limiter_api.h | 125 + .../limiter/lib/inc/limiter_calibration_api.h | 95 + .../gain_control/limiter/lib/src/limiter.c | 1461 ++++++++++++ .../gain_control/limiter/lib/src/limiter.h | 87 + .../gain_control/limiter/lib/src/limiter24.c | 1042 +++++++++ .../gain_control/limiter/lib/src/limiter24.h | 105 + scripts/cmake/spf.cmake | 64 +- 42 files changed, 12138 insertions(+), 2 deletions(-) create mode 100644 modules/cmn/common/internal_api/imcl_drc_info_api.h create mode 100644 modules/cmn/common/utils/inc/audio_apiir_df1_opt.h create mode 100644 modules/cmn/common/utils/src/apiir_df1_opt.c create mode 100644 modules/processing/gain_control/drc/api/api_drc.h create mode 100644 modules/processing/gain_control/drc/build/CMakeLists.txt create mode 100644 modules/processing/gain_control/drc/capi/inc/capi_drc.h create mode 100644 modules/processing/gain_control/drc/capi/src/capi_drc.cpp create mode 100644 modules/processing/gain_control/drc/capi/src/capi_drc_utils.cpp create mode 100644 modules/processing/gain_control/drc/capi/src/capi_drc_utils.h create mode 100644 modules/processing/gain_control/drc/lib/inc/CDrcLib.h create mode 100644 modules/processing/gain_control/drc/lib/inc/drc_api.h create mode 100644 modules/processing/gain_control/drc/lib/inc/drc_calib_api.h create mode 100644 modules/processing/gain_control/drc/lib/src/CDrcLib.cpp create mode 100644 modules/processing/gain_control/drc/lib/src/drc_lib.c create mode 100644 modules/processing/gain_control/drc/lib/src/drc_lib.h create mode 100644 modules/processing/gain_control/drc/lib/src/drc_lib_opt.c create mode 100644 modules/processing/gain_control/drc/lib/stub_src/CDrcLib_stub.cpp create mode 100644 modules/processing/gain_control/iir_mbdrc/api/mbdrc_api.h create mode 100644 modules/processing/gain_control/iir_mbdrc/bin/arm/libiir_mbdrc.a create mode 100644 modules/processing/gain_control/iir_mbdrc/build/CMakeLists.txt create mode 100644 modules/processing/gain_control/iir_mbdrc/inc/capi_iir_mbdrc.h create mode 100644 modules/processing/gain_control/iir_mbdrc/inc/capi_iir_mbdrc_utils.h create mode 100644 modules/processing/gain_control/iir_mbdrc/inc/iir_mbdrc.h create mode 100644 modules/processing/gain_control/iir_mbdrc/inc/iir_mbdrc_api.h create mode 100644 modules/processing/gain_control/iir_mbdrc/inc/iir_mbdrc_calibration_api.h create mode 100644 modules/processing/gain_control/iir_mbdrc/inc/iir_mbdrc_function_defines.h create mode 100644 modules/processing/gain_control/limiter/api/api_limiter.h create mode 100644 modules/processing/gain_control/limiter/build/CMakeLists.txt create mode 100644 modules/processing/gain_control/limiter/lib/build/CMakeLists.txt create mode 100644 modules/processing/gain_control/limiter/lib/inc/limiter24_api.h create mode 100644 modules/processing/gain_control/limiter/lib/inc/limiter_api.h create mode 100644 modules/processing/gain_control/limiter/lib/inc/limiter_calibration_api.h create mode 100644 modules/processing/gain_control/limiter/lib/src/limiter.c create mode 100644 modules/processing/gain_control/limiter/lib/src/limiter.h create mode 100644 modules/processing/gain_control/limiter/lib/src/limiter24.c create mode 100644 modules/processing/gain_control/limiter/lib/src/limiter24.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d8bdc70..92db96c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,6 +81,9 @@ modules/audio/pcm_decoder/api modules/audio/pcm_encoder/api modules/cmn/pcm_mf_cnv/capi/mfc/api modules/cmn/pcm_mf_cnv/capi/pcm_cnv/api +modules/processing/gain_control/drc/api +modules/processing/gain_control/iir_mbdrc/api +modules/cmn/api ) ### ### Some strings used for picking inc paths based on arch, tgt/sim & static/shared. diff --git a/arch/linux/configs/defconfig b/arch/linux/configs/defconfig index 3a8c7e0..f3011cf 100644 --- a/arch/linux/configs/defconfig +++ b/arch/linux/configs/defconfig @@ -18,6 +18,8 @@ CONFIG_PCM_CNV=y CONFIG_MFC=y CONFIG_PCM_DECODER=y CONFIG_PCM_ENCODER=y +CONFIG_DRC=y +CONFIG_IIR_MBDRC=y # # Signal Processing Framework diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index 48a0e30..0def0f9 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -34,3 +34,10 @@ endif() if(CONFIG_PCM_CNV) add_subdirectory(cmn/pcm_mf_cnv/build) endif() +if(CONFIG_DRC) + add_subdirectory(processing/gain_control/drc/build) + add_subdirectory(processing/gain_control/limiter/build) +endif() +if(CONFIG_IIR_MBDRC) + add_subdirectory(processing/gain_control/iir_mbdrc/build) +endif() diff --git a/modules/Kconfig b/modules/Kconfig index f16996c..757487b 100644 --- a/modules/Kconfig +++ b/modules/Kconfig @@ -35,4 +35,13 @@ config PCM_CNV select CH_MIXER select MSIIR default y + +config DRC + tristate "Enable DRC Library" + default y + +config IIR_MBDRC + tristate "Enable IIR_MBDRC Library" + select DRC + default y endmenu diff --git a/modules/cmn/common/internal_api/imcl_drc_info_api.h b/modules/cmn/common/internal_api/imcl_drc_info_api.h new file mode 100644 index 0000000..3553990 --- /dev/null +++ b/modules/cmn/common/internal_api/imcl_drc_info_api.h @@ -0,0 +1,72 @@ +#ifndef IMCL_DRC_INFO_API_H +#define IMCL_DRC_INFO_API_H + +/** + @file imcl_drc_info_api.h + + @brief defines the Intent ID for communicating DRC parameters + over Inter-Module Control Links (IMCL). + +*/ + +/*========================================================================== + * Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + * SPDX-License-Identifier: BSD-3-Clause + * =========================================================================*/ + +#define INTENT_ID_DRC_CONFIG 0x080010F3 +#ifdef INTENT_ID_DRC_CONFIG + +#ifdef __cplusplus +extern "C" { +#endif /*__cplusplus*/ + + +/*============================================================================== + Intent ID - INTENT_ID_DRC_CONFIG +==============================================================================*/ +/**< +Intent ID to send DRC parameters over control link. + +e.g. +Control link between Rx DRC and Tx AVC modules in voice path. +*/ + +/* ============================================================================ + Param ID +==============================================================================*/ + +#define PARAM_ID_IMCL_DRC_DOWN_COMP_THRESHOLD 0x080010F4 + +/*============================================================================== + Param structure defintions +==============================================================================*/ + +/* Structure definition for Parameter */ +typedef struct param_id_imcl_drc_down_comp_threshold_t param_id_imcl_drc_down_comp_threshold_t; + +/** @h2xmlp_parameter {"PARAM_ID_IMCL_DRC_DOWN_COMP_THRESHOLD", + PARAM_ID_IMCL_DRC_DOWN_COMP_THRESHOLD} + @h2xmlp_description {This parameter is used to send the drc down + compression threshold over the control link.} + @h2xmlp_toolPolicy {NO_SUPPORT} */ + +#include "spf_begin_pack.h" +struct param_id_imcl_drc_down_comp_threshold_t +{ + int16_t drc_rx_enable; + /**< @h2xmle_description {Specifies whether DRC module is enabled.} */ + + int16_t down_comp_threshold_L16Q7; + /**< @h2xmle_description {DRC down-compression ratio in Q7 format.} */ +} +#include "spf_end_pack.h" +; + +#ifdef __cplusplus +} +#endif /*__cplusplus*/ + +#endif // INTENT_ID_DRC_CONFIG + +#endif /* IMCL_DRC_INFO_API_H */ diff --git a/modules/cmn/common/utils/build/CMakeLists.txt b/modules/cmn/common/utils/build/CMakeLists.txt index f5821c9..709cc72 100644 --- a/modules/cmn/common/utils/build/CMakeLists.txt +++ b/modules/cmn/common/utils/build/CMakeLists.txt @@ -36,6 +36,7 @@ set (lib_srcs_list ${LIB_ROOT}/src/util.c ${LIB_ROOT}/src/audio_delay32.c ${LIB_ROOT}/src/clips.c + ${LIB_ROOT}/src/apiir_df1_opt.c ) #Call spf_build_static_library to generate the static library diff --git a/modules/cmn/common/utils/inc/audio_apiir_df1_opt.h b/modules/cmn/common/utils/inc/audio_apiir_df1_opt.h new file mode 100644 index 0000000..155b657 --- /dev/null +++ b/modules/cmn/common/utils/inc/audio_apiir_df1_opt.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ + +/* FILE NAME: audio_apiir_df1_opt.h TYPE: C-header file *] + +[* DESCRIPTION: Contains global variables and fn declarations for equalizer. *] + +[* when who what, where, why *] + +[* -------- --- -----------------------------------------------------*] + +[* 07/2015 hxia Initial revision *] + +[*****************************************************************************/ + + + +#ifndef _AUDIO_IIRDF1_H_ + +#define _AUDIO_IIRDF1_H_ + + + +#include "ar_defs.h" + + + +/*============================================================================= + + Function Declarations + +=============================================================================*/ + + + +#ifdef __cplusplus + +extern "C" { + +#endif /* __cplusplus */ + + + +/* To use the following function, it should be ensured that the Q-factor of den and */ + +/* num coefs should be <= Q27, in order to avoid overflow */ + +/* The suggested Q-factors are: */ + +/* Q1.27, Q2.26, Q3.25, Q4.24... */ + + + +/* This is the df1 allpass biquad iir function */ + +/* Only b0 and b1 is needed, using transfer function: */ + +/* H_ap(z) = (b0 + b1*z^-1 + z^-2)/(1 + b1*z^-1 + b0*z^-2) */ + +void iirDF1_32_ap(int32_t *inp, + + int32_t *out, + + int32_t samples, + + int32_t *numcoefs, + + int32_t *mem, + + int16_t qfactor); + + + + + +#ifdef __cplusplus + +} + +#endif /* __cplusplus */ + + + +#endif /* _AUDIO_IIRDF1_H_ */ + diff --git a/modules/cmn/common/utils/src/apiir_df1_opt.c b/modules/cmn/common/utils/src/apiir_df1_opt.c new file mode 100644 index 0000000..419e57c --- /dev/null +++ b/modules/cmn/common/utils/src/apiir_df1_opt.c @@ -0,0 +1,205 @@ +/* + * Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ + +/*****************************************************************************] +[* FILE NAME: audio_apiir_df1_opt.h TYPE: C-header file *] + +[* DESCRIPTION: Contains global variables and fn declarations for equalizer. *] +[*****************************************************************************/ + + + +/* This function calculates the response of a two-pole allpass IIR filter */ + +/* that is implemented in DF1 form */ + +/* numcoefs->b(0), only 2 coefs are stored */ + + + +#include "audio_apiir_df1_opt.h" + +#include "audio_basic_op.h" + +#include "ar_defs.h" + +/* To use the following function, it should be ensured that the Q-factor of den and */ + +/* num coefs should be <= Q27, in order to avoid overflow */ + +/* The suggested Q-factors are: */ + +/* Q1.27, Q2.26, Q3.25, Q4.24... */ + + +#ifndef IIR_DF1_32_AP_ASM +void iirDF1_32_ap(int32_t *inp, + + int32_t *out, + + int32_t samples, + + int32_t *numcoefs, + + int32_t *mem, + + int16_t qfactor) + +{ + + int64_t y, ff, fb; + + int32_t yScaled; // yScaled will be the scaled version of y used while calculating w1 and w2 + + + + int64_t b0TimesX, b1TimesW1, b2TimesW2; + + int64_t a1TimesW3, a2TimesW4; + + int32_t w1Temp, w3Temp, w4Temp; + + int64_t w2Temp; + + + + int32_t b0, b1; + + int32_t i, p5; + + + + if (qfactor >= 1){ + + p5 = (int32_t)(1 << (qfactor - 1)); + + } else + + { + + p5 = 0; + + } + + + + b0 = *numcoefs; + + b1 = *(numcoefs + 1); + + + + /* repeat the loop for every sample*/ + + /* in allpass biquad, a1 = b1, a2 = b0, b2 = 1 */ + + /* equations are ff= b0*x+b1*w1+b2*w2 */ // use yScaled + + /* i.e. ff= b0*x+b1*w1+w2 */ + + /* w2=w1 */ + + /* w1=x */ + + /* fb= a1*w3+a2*w4 */ // use yScaled + + /* i.e. fb= b1*w3+b0*w4 */ + + /* w4=w3 */ + + /* y = ff - fb */ + + /* w3=y */ + + + + + + for (i = 0; i < samples; ++i) + + { + + /******************************* calculate ff ********************************/ + + b0TimesX = s64_mult_s32_s32(b0, *inp); // Q(27 + qfactor) + + + + w1Temp = *mem++; // after this, mem points to w2 // Q27 + + b1TimesW1 = s64_mult_s32_s32(b1, w1Temp); // Q(27 + qfactor) + + + + ff = s64_add_s64_s64(b0TimesX, b1TimesW1); // Q(27 + qfactor) + + + + w2Temp = (int64_t)*mem; // after this, mem points to w2 // Q27 + + b2TimesW2 = s64_shl_s64(w2Temp, qfactor); // Q(27 + qfactor) + + + + ff = s64_add_s64_s64(ff, b2TimesW2); // Q(27 + qfactor) + + + + *mem-- = w1Temp; // after this, mem points to w1 // w2 = w1, Q27 + + *mem = *inp++; // w1 = x + + // inp incremented to next input location in the above line + + mem += 2; // after this, mem points to w3 + + + + /******************************* calculate fb *******************************/ + + w3Temp = *mem++; // after this, mem points to w4 // Q27 + + a1TimesW3 = s64_mult_s32_s32(b1, w3Temp); // Q(27 + qfactor) + + + + w4Temp = *mem; // after this, mem points to w4 // Q27 + + a2TimesW4 = s64_mult_s32_s32(b0, w4Temp); // Q(27 + qfactor) + + + + fb = s64_add_s64_s64(a1TimesW3, a2TimesW4); // Q(27 + qfactor) + + + + *mem-- = w3Temp; // after this, mem points to w3 // w4 = w3 + + + + /******************************* calculate y *******************************/ + + y = s64_sub_s64_s64(ff, fb); // Q(27 + qfactor) + + yScaled = s32_saturate_s64(s64_shl_s64(s64_add_s64_s32(y, p5), -qfactor)); // Q27 + + + + *mem = yScaled; // Q27 + + mem -= 2; // after this, mem points to w1 + + + + /***************************** store the output ******************************/ + + *out++ = yScaled; // Q27 + + /* Performing this here in order to support in-place computation */ + + } + +} +#endif diff --git a/modules/processing/gain_control/drc/api/api_drc.h b/modules/processing/gain_control/drc/api/api_drc.h new file mode 100644 index 0000000..22e2a6a --- /dev/null +++ b/modules/processing/gain_control/drc/api/api_drc.h @@ -0,0 +1,289 @@ +#ifndef API_DRC_H +#define API_DRC_H +/*============================================================================== + @file drc_api.h + @brief This file contains DRC APIs +==============================================================================*/ +/*======================================================================= + * Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + * SPDX-License-Identifier: BSD-3-Clause-Clear +=========================================================================*/ + +/*============================================================================== + Include Files +==============================================================================*/ + +#include "module_cmn_api.h" +#include "imcl_spm_intent_api.h" + +/*============================================================================== + Constants +==============================================================================*/ + +/* Stack size of DRC module */ +#define DRC_STACK_SIZE 2048 + + +/*============================================================================== + Param ID +==============================================================================*/ + +#define PARAM_ID_DRC_CONFIG 0x0800113E +/** @h2xmlp_parameter {"PARAM_ID_DRC_CONFIG", PARAM_ID_DRC_CONFIG} + @h2xmlp_description {DRC config parameters.} + @h2xmlp_toolPolicy {Calibration; RTC} */ + +typedef struct param_id_drc_config_t param_id_drc_config_t; + +#include "spf_begin_pack.h" +struct param_id_drc_config_t +{ + uint16_t mode; + /**< @h2xmle_description {Enable/disables the DRC feature. In Bypass mode, only delay is applied.} + @h2xmle_rangeList {"Bypass";0, "Enable";1} + @h2xmle_default {1} + @h2xmle_policy {Basic}*/ + + uint16_t channel_linked_mode; + /**< @h2xmle_description {Specifies whether all channels have the same applied dynamics(Linked) + or if they process their dynamics independently(Unlinked).} + @h2xmle_rangeList {"CHANNEL_UNLINKED";0, "CHANNEL_LINKED";1} + @h2xmle_default {0} + @h2xmle_policy {Basic}*/ + + uint16_t rms_time_avg_const; + /**< @h2xmle_description {Time-averaging constant for computing energy} + @h2xmle_range {0..65535} + @h2xmle_dataFormat {Q16} + @h2xmle_default {299} + @h2xmle_policy {Basic} */ + + uint16_t makeup_gain; + /**< @h2xmle_description {Makeup gain in dB applied after DRC.} + @h2xmle_range {0..65535} + @h2xmle_dataFormat {Q12} + @h2xmle_default {4096} + @h2xmle_displayType {dbtextbox} + @h2xmle_policy {Basic} */ + + int16_t down_expdr_threshold; + /**< @h2xmle_description {Downward expander threshold in dB} + @h2xmle_range {-32768..32767} + @h2xmle_dataFormat {Q7} + @h2xmle_default {3239} + @h2xmle_policy {Basic} */ + + int16_t down_expdr_slope; + /**< @h2xmle_description {Downward expander slope.} + @h2xmle_range {-32768..32767} + @h2xmle_dataFormat {Q8} + @h2xmle_default {-383} + @h2xmle_policy {Basic} */ + + uint32_t down_expdr_attack; + /**< @h2xmle_description {Down expander attack constant.} + @h2xmle_range {0..2147483648} + @h2xmle_dataFormat {Q31} + @h2xmle_default {1604514739} + @h2xmle_policy {Basic} */ + + uint32_t down_expdr_release; + /**< @h2xmle_description {Down expander release constant.} + @h2xmle_range {0..2147483648} + @h2xmle_dataFormat {Q31} + @h2xmle_default {1604514739} + @h2xmle_policy {Basic} */ + + int32_t down_expdr_min_gain_db; + /**< @h2xmle_description {Downward expander minimum gain in dB.} + @h2xmle_range {-2147483648..2147483647} + @h2xmle_dataFormat {Q23} + @h2xmle_default {-50331648} + @h2xmle_policy {Basic} */ + + uint16_t down_expdr_hysteresis; + /**< @h2xmle_description {Down expander hysteresis constant.} + @h2xmle_range {0..65535} + @h2xmle_dataFormat {Q14} + @h2xmle_default {16384} + @h2xmle_policy {Basic} */ + + int16_t up_cmpsr_threshold; + /**< @h2xmle_description {Upward compressor threshold.} + @h2xmle_range {-32768..32767} + @h2xmle_dataFormat {Q7} + @h2xmle_default {3239} + @h2xmle_policy {Basic} */ + + uint32_t up_cmpsr_attack; + /**< @h2xmle_description {Up compressor attack constant.} + @h2xmle_range {0..2147483648} + @h2xmle_dataFormat {Q31} + @h2xmle_default {5897467} + @h2xmle_policy {Basic} */ + + uint32_t up_cmpsr_release; + /**< @h2xmle_description {Up compressor release constant.} + @h2xmle_range {0..2147483648} + @h2xmle_dataFormat {Q31} + @h2xmle_default {5897467} + @h2xmle_policy {Basic} */ + + uint16_t up_cmpsr_slope; + /**< @h2xmle_description {Upward compression slope.} + @h2xmle_range {0..65535} + @h2xmle_dataFormat {Q16} + @h2xmle_default {0} + @h2xmle_policy {Basic} */ + + uint16_t up_cmpsr_hysteresis; + /**< @h2xmle_description {Up compressor hysteresis constant.} + @h2xmle_range {0..65535} + @h2xmle_dataFormat {Q14} + @h2xmle_default {18855} + @h2xmle_policy {Basic} */ + + int16_t down_cmpsr_threshold; + /**< @h2xmle_description {Downward compressor threshold.} + @h2xmle_range {-32768..32767} + @h2xmle_dataFormat {Q7} + @h2xmle_default {9063} + @h2xmle_policy {Basic} */ + + uint16_t down_cmpsr_slope; + /**< @h2xmle_description {Downward compression slope.} + @h2xmle_range {0..65535} + @h2xmle_dataFormat {Q16} + @h2xmle_default {64879} + @h2xmle_policy {Basic} */ + + uint32_t down_cmpsr_attack; + /**< @h2xmle_description {Down compressor attack constant.} + @h2xmle_range {0..2147483648} + @h2xmle_dataFormat {Q31} + @h2xmle_default {1604514739} + @h2xmle_policy {Basic} */ + + uint32_t down_cmpsr_release; + /**< @h2xmle_description {Down compressor release constant.} + @h2xmle_range {0..2147483648} + @h2xmle_dataFormat {Q31} + @h2xmle_default {1604514739} + @h2xmle_policy {Basic} */ + + uint16_t down_cmpsr_hysteresis; + /**< @h2xmle_description {Down compressor hysteresis constant.} + @h2xmle_range {0..65535} + @h2xmle_dataFormat {Q14} + @h2xmle_default {16384} + @h2xmle_policy {Basic} */ + + uint16_t down_sample_level; + /**< @h2xmle_description {DRC down sample level.} + @h2xmle_range {1..16} + @h2xmle_default {1} + @h2xmle_displayType {slider} + @h2xmle_policy {Basic} */ + + uint32_t delay_us; + /**< @h2xmle_description {DRC delay in microseconds.} + @h2xmle_range {0..100000} + @h2xmle_default {5000} + @h2xmle_policy {Basic} */ +} +#include "spf_end_pack.h" +; + +/*------------------------------------------------------------------------------ + Module +------------------------------------------------------------------------------*/ + +#define MODULE_ID_DRC 0x07001066 +/** + @h2xmlm_module {"MODULE_ID_DRC", MODULE_ID_DRC} + @h2xmlm_displayName {"Dynamic Range Control"} + @h2xmlm_modSearchKeys{drc, Audio, Voice} + @h2xmlm_description { - Dynamic Range Control Algorithm. + - This module supports the following parameter IDs:\n + - #PARAM_ID_MODULE_ENABLE\n + - #PARAM_ID_DRC_CONFIG\n + - Supported Input Media Format: \n + - Data Format : FIXED \n + - fmt_id : PCM \n + - Sample Rates : Don't care \n + - Number of channels : 1...32 \n + - Channel type : Don't care \n + - Bits per sample : 16, 32 \n + - Q format : Q15, Q27 \n + - Interleaving : De-interleaved unpacked \n + - Signed/unsigned : Signed \n} + @h2xmlm_dataMaxInputPorts {1} + @h2xmlm_dataMaxOutputPorts {1} + @h2xmlm_supportedContTypes {APM_CONTAINER_TYPE_SC, APM_CONTAINER_TYPE_GC} + @h2xmlm_isOffloadable {true} + @h2xmlm_stackSize {DRC_STACK_SIZE} + @h2xmlm_ctrlDynamicPortIntent { "DRC DOWN COMP threshold" = INTENT_ID_DRC_CONFIG, maxPorts= 1 } + @h2xmlm_toolPolicy { Calibration } + + @{ <-- Start of the Module --> + + @h2xml_Select {param_id_module_enable_t} + @h2xmlm_InsertParameter + + @h2xml_Select {param_id_drc_config_t} + @h2xmlm_InsertParameter + + @h2xml_Select {param_id_drc_config_t::rms_time_avg_const} + + @h2xmle_defaultDependency { Samplerate = "8000", default = "598"} + @h2xmle_defaultDependency { Samplerate = "16000", default = "299"} + @h2xmle_defaultDependency { Samplerate = "32000", default = "1484"} + @h2xmle_defaultDependency { Samplerate = "48000", default = "993"} + + @h2xml_Select {param_id_drc_config_t::down_expdr_attack} + + @h2xmle_defaultDependency { Samplerate = "8000", default = "2010199605"} + @h2xmle_defaultDependency { Samplerate = "16000", default = "1604514739"} + @h2xmle_defaultDependency { Samplerate = "32000", default = "1067661045"} + @h2xmle_defaultDependency { Samplerate = "48000", default = "789550996"} + + @h2xml_Select {param_id_drc_config_t::down_expdr_release} + + @h2xmle_defaultDependency { Samplerate = "8000", default = "2010199605"} + @h2xmle_defaultDependency { Samplerate = "16000", default = "1604514739"} + @h2xmle_defaultDependency { Samplerate = "32000", default = "1067661045"} + @h2xmle_defaultDependency { Samplerate = "48000", default = "789550996"} + + @h2xml_Select {param_id_drc_config_t::up_cmpsr_attack} + + @h2xmle_defaultDependency { Samplerate = "8000", default = "5897467"} + @h2xmle_defaultDependency { Samplerate = "16000", default = "5897467"} + @h2xmle_defaultDependency { Samplerate = "32000", default = "2950761"} + @h2xmle_defaultDependency { Samplerate = "48000", default = "1967625"} + + @h2xml_Select {param_id_drc_config_t::up_cmpsr_release} + + @h2xmle_defaultDependency { Samplerate = "8000", default = "5897468"} + @h2xmle_defaultDependency { Samplerate = "16000", default = "5897467"} + @h2xmle_defaultDependency { Samplerate = "32000", default = "2950761"} + @h2xmle_defaultDependency { Samplerate = "48000", default = "1967625"} + + @h2xml_Select {param_id_drc_config_t::down_cmpsr_attack} + + @h2xmle_defaultDependency { Samplerate = "8000", default = "2010199605"} + @h2xmle_defaultDependency { Samplerate = "16000", default = "1604514739"} + @h2xmle_defaultDependency { Samplerate = "32000", default = "1067661045"} + @h2xmle_defaultDependency { Samplerate = "48000", default = "789550996"} + + @h2xml_Select {param_id_drc_config_t::down_cmpsr_release} + + @h2xmle_defaultDependency { Samplerate = "8000", default = "2010199605"} + @h2xmle_defaultDependency { Samplerate = "16000", default = "1604514739"} + @h2xmle_defaultDependency { Samplerate = "32000", default = "1067661045"} + @h2xmle_defaultDependency { Samplerate = "48000", default = "789550996"} + +@} <-- End of the Module -->*/ + + + +#endif diff --git a/modules/processing/gain_control/drc/build/CMakeLists.txt b/modules/processing/gain_control/drc/build/CMakeLists.txt new file mode 100644 index 0000000..fcc888d --- /dev/null +++ b/modules/processing/gain_control/drc/build/CMakeLists.txt @@ -0,0 +1,45 @@ +#[[ + @file CMakeLists.txt + + @brief + + @copyright + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause +]] +cmake_minimum_required(VERSION 3.10) + +set(drc_sources + ${LIB_ROOT}/capi/src/capi_drc.cpp + ${LIB_ROOT}/capi/src/capi_drc_utils.cpp + ${LIB_ROOT}/lib/src/drc_lib.c + ${LIB_ROOT}/lib/src/drc_lib_opt.c +) + +set(drc_includes + ${LIB_ROOT}/api + ${LIB_ROOT}/capi/inc + ${LIB_ROOT}/capi/src + ${LIB_ROOT}/lib/inc + ${LIB_ROOT}/lib/src + ../../../../cmn/common/utils/inc + ../../../../cmn/api + ../../../../cmn/common/internal_api +) + +spf_module_sources( + KCONFIG CONFIG_DRC + NAME drc + MAJOR_VER 1 + MINOR_VER 0 + AMDB_ITYPE "capi" + AMDB_MTYPE "PP" + AMDB_MID "0x07001066" + AMDB_TAG "capi_drc" + AMDB_MOD_NAME "MODULE_ID_DRC" + AMDB_FMT_ID1 "MEDIA_FMT_ID_PCM" + SRCS ${drc_sources} + INCLUDES ${drc_includes} + H2XML_HEADERS "../api/api_drc.h" + CFLAGS "" +) diff --git a/modules/processing/gain_control/drc/capi/inc/capi_drc.h b/modules/processing/gain_control/drc/capi/inc/capi_drc.h new file mode 100644 index 0000000..3e8dcb1 --- /dev/null +++ b/modules/processing/gain_control/drc/capi/inc/capi_drc.h @@ -0,0 +1,37 @@ +/** +@file capi_drc.h + +@brief CAPI V2 API wrapper for drc algorithm + +*/ + +/*----------------------------------------------------------------------- +Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. +SPDX-License-Identifier: BSD-3-Clause +-----------------------------------------------------------------------*/ + +#ifndef CAPI_DRC +#define CAPI_DRC + +#ifdef __cplusplus +extern "C" { +#endif //__cplusplus + +/*---------------------------------------------------------------------------- + * Include Files + * -------------------------------------------------------------------------*/ +#include "capi.h" + +/*---------------------------------------------------------------------------- + * Function Declarations + * -------------------------------------------------------------------------*/ + +capi_err_t capi_drc_get_static_properties(capi_proplist_t *init_set_properties, + capi_proplist_t *static_properties); + +capi_err_t capi_drc_init(capi_t *_pif, capi_proplist_t *init_set_properties); + +#ifdef __cplusplus +} +#endif //__cplusplus +#endif // CAPI_DRC diff --git a/modules/processing/gain_control/drc/capi/src/capi_drc.cpp b/modules/processing/gain_control/drc/capi/src/capi_drc.cpp new file mode 100644 index 0000000..69b50dc --- /dev/null +++ b/modules/processing/gain_control/drc/capi/src/capi_drc.cpp @@ -0,0 +1,622 @@ +/** +@file capi_drc.cpp +@brief CAPI V2 API wrapper for DRC algorithm + + */ + +/*------------------------------------------------------------------------------ + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause +-------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------ + * Include Files + * ----------------------------------------------------------------------------*/ +#include "capi_drc.h" + +#include "capi_drc_utils.h" + +/*------------------------------------------------------------------------ + * Static declarations + * -----------------------------------------------------------------------*/ +static capi_err_t capi_process(capi_t *_pif, capi_stream_data_t *input[], capi_stream_data_t *output[]); +static capi_err_t capi_end(capi_t *_pif); +static capi_err_t capi_set_param(capi_t * _pif, + uint32_t param_id, + const capi_port_info_t *port_info_ptr, + capi_buf_t * params_ptr); +static capi_err_t capi_get_param(capi_t * _pif, + uint32_t param_id, + const capi_port_info_t *port_info_ptr, + capi_buf_t * params_ptr); +static capi_err_t capi_set_properties(capi_t *_pif, capi_proplist_t *props_ptr); +static capi_err_t capi_get_properties(capi_t *_pif, capi_proplist_t *props_ptr); + +static capi_vtbl_t vtbl = { capi_process, capi_end, capi_set_param, capi_get_param, + capi_set_properties, capi_get_properties }; + +/*---------------------------------------------------------------------------- + * Function Definitions + * -------------------------------------------------------------------------*/ +capi_err_t capi_drc_get_static_properties(capi_proplist_t *init_props_ptr, capi_proplist_t *out_props_ptr) +{ + return capi_get_properties(NULL, out_props_ptr); +} + +capi_err_t capi_drc_init(capi_t *_pif, capi_proplist_t *init_props_ptr) +{ + capi_err_t result = CAPI_EOK; + capi_drc_t *me_ptr = (capi_drc_t *)_pif; + + result |= (NULL == _pif) ? CAPI_EFAILED : result; + CHECK_THROW_ERROR(MIID_UNKNOWN, result, "Init received null pointer."); + + memset(me_ptr, 0, sizeof(capi_drc_t)); + + drc_lib_set_default_config(me_ptr); + + if (init_props_ptr) + { + result = capi_set_properties(_pif, init_props_ptr); + + // Ignoring non-fatal error code. + result ^= (result & CAPI_EUNSUPPORTED); + CHECK_THROW_ERROR(MIID_UNKNOWN, result, "Init set properties failed."); + } + + me_ptr->vtbl = &vtbl; + + capi_cmn_update_process_check_event(&me_ptr->cb_info, FALSE); + + capi_cmn_ctrl_port_list_init(&me_ptr->ctrl_port_info); + + DRC_MSG(me_ptr->miid, DBG_LOW_PRIO, "Init done, status 0x%x.", result); + + return result; +} + +static capi_err_t capi_set_param(capi_t * _pif, + uint32_t param_id, + const capi_port_info_t *port_info_ptr, + capi_buf_t * params_ptr) +{ + capi_err_t result = CAPI_EOK; + capi_drc_t *me_ptr = (capi_drc_t *)_pif; + + result |= (NULL == _pif || NULL == params_ptr || NULL == params_ptr->data_ptr) ? CAPI_EFAILED : result; + CHECK_THROW_ERROR(MIID_UNKNOWN, result, "setparam received bad pointer."); + + switch (param_id) + { + case INTF_EXTN_PARAM_ID_IMCL_PORT_OPERATION: { + uint32_t supported_intent[1] = { INTENT_ID_DRC_CONFIG }; + result = capi_cmn_ctrl_port_operation_handler(&me_ptr->ctrl_port_info, + params_ptr, + (POSAL_HEAP_ID)me_ptr->heap_id, + 0, + 1, + supported_intent); + break; + } + case PARAM_ID_MODULE_ENABLE: { + result |= (params_ptr->actual_data_len < sizeof(param_id_module_enable_t)) ? CAPI_ENEEDMORE : result; + CHECK_THROW_ERROR(me_ptr->miid, + result, + "Insufficient size for enable/disable parameter. Received %lu bytes", + params_ptr->actual_data_len); + param_id_module_enable_t *en_dis_ptr = (param_id_module_enable_t *)params_ptr->data_ptr; + + if (me_ptr->b_enable != en_dis_ptr->enable) + { + me_ptr->b_enable = en_dis_ptr->enable; + + if (me_ptr->b_enable) + { + if (NULL != me_ptr->lib_handle.lib_mem_ptr) + { + drc_set_param(&me_ptr->lib_handle, DRC_PARAM_SET_RESET, NULL, 0); + } + } + + raise_kpps_delay_process_events(me_ptr); + } + break; + } + case PARAM_ID_DRC_CONFIG: { + result |= (params_ptr->actual_data_len < sizeof(param_id_drc_config_t)) ? CAPI_ENEEDMORE : result; + CHECK_THROW_ERROR(me_ptr->miid, + result, + "Insufficient size for drc config parameter. Received %lu bytes", + params_ptr->actual_data_len); + param_id_drc_config_t *drc_config_ptr = (param_id_drc_config_t *)params_ptr->data_ptr; + + // copy drc configuration data to wrapper structure + me_ptr->lib_cfg.channelLinked = drc_config_ptr->channel_linked_mode; + me_ptr->lib_cfg.downSampleLevel = drc_config_ptr->down_sample_level; + me_ptr->lib_cfg.dnCompAttackUL32Q31 = drc_config_ptr->down_cmpsr_attack; + me_ptr->lib_cfg.dnCompHysterisisUL16Q14 = drc_config_ptr->down_cmpsr_hysteresis; + me_ptr->lib_cfg.dnCompReleaseUL32Q31 = drc_config_ptr->down_cmpsr_release; + me_ptr->lib_cfg.dnCompSlopeUL16Q16 = drc_config_ptr->down_cmpsr_slope; + me_ptr->lib_cfg.dnCompThresholdL16Q7 = drc_config_ptr->down_cmpsr_threshold; + me_ptr->lib_cfg.dnExpaAttackUL32Q31 = drc_config_ptr->down_expdr_attack; + me_ptr->lib_cfg.dnExpaHysterisisUL16Q14 = drc_config_ptr->down_expdr_hysteresis; + me_ptr->lib_cfg.dnExpaMinGainDBL32Q23 = drc_config_ptr->down_expdr_min_gain_db; + me_ptr->lib_cfg.dnExpaReleaseUL32Q31 = drc_config_ptr->down_expdr_release; + me_ptr->lib_cfg.dnExpaSlopeL16Q8 = drc_config_ptr->down_expdr_slope; + me_ptr->lib_cfg.dnExpaThresholdL16Q7 = drc_config_ptr->down_expdr_threshold; + me_ptr->lib_cfg.makeupGainUL16Q12 = drc_config_ptr->makeup_gain; + me_ptr->lib_cfg.rmsTavUL16Q16 = drc_config_ptr->rms_time_avg_const; + me_ptr->lib_cfg.upCompAttackUL32Q31 = drc_config_ptr->up_cmpsr_attack; + me_ptr->lib_cfg.upCompHysterisisUL16Q14 = drc_config_ptr->up_cmpsr_hysteresis; + me_ptr->lib_cfg.upCompReleaseUL32Q31 = drc_config_ptr->up_cmpsr_release; + me_ptr->lib_cfg.upCompSlopeUL16Q16 = drc_config_ptr->up_cmpsr_slope; + me_ptr->lib_cfg.upCompThresholdL16Q7 = drc_config_ptr->up_cmpsr_threshold; + + me_ptr->mode = (drc_config_ptr->mode) ? DRC_ENABLED : DRC_BYPASSED; + + // check if delay is changed + if (me_ptr->delay_us != drc_config_ptr->delay_us) + { + me_ptr->delay_us = drc_config_ptr->delay_us; + + result = drc_lib_alloc_init(me_ptr); + CHECK_THROW_ERROR(me_ptr->miid, result, "Lib allocation failed."); + } + + if (me_ptr->lib_handle.lib_mem_ptr) + { + result = drc_lib_set_calib(me_ptr); + CHECK_THROW_ERROR(me_ptr->miid, result, "Lib setparam failed."); + + raise_kpps_delay_process_events(me_ptr); + } + + break; + } + default: { + DRC_MSG(me_ptr->miid, DBG_ERROR_PRIO, "Invalid setparam received 0x%lx", param_id); + return CAPI_EUNSUPPORTED; + } + } + + drc_lib_send_config_imcl(me_ptr); + + return result; +} + +static capi_err_t capi_get_param(capi_t * _pif, + uint32_t param_id, + const capi_port_info_t *port_info_ptr, + capi_buf_t * params_ptr) +{ + capi_err_t result = CAPI_EOK; + capi_drc_t *me_ptr = (capi_drc_t *)_pif; + + result |= (NULL == _pif || NULL == params_ptr || NULL == params_ptr->data_ptr) ? CAPI_EFAILED : result; + CHECK_THROW_ERROR(MIID_UNKNOWN, result, "getparam received bad pointer."); + + params_ptr->actual_data_len = 0; + switch (param_id) + { + case PARAM_ID_MODULE_ENABLE: { + result |= (params_ptr->max_data_len < sizeof(param_id_module_enable_t)) ? CAPI_ENEEDMORE : result; + CHECK_THROW_ERROR(me_ptr->miid, + result, + "Insufficient size for enable/disable parameter. Received %lu bytes", + params_ptr->max_data_len); + param_id_module_enable_t *en_dis_ptr = (param_id_module_enable_t *)params_ptr->data_ptr; + memset(en_dis_ptr, 0, sizeof(param_id_module_enable_t)); + en_dis_ptr->enable = me_ptr->b_enable; + + params_ptr->actual_data_len = sizeof(param_id_module_enable_t); + + break; + } + case PARAM_ID_DRC_CONFIG: { + result |= (params_ptr->max_data_len < sizeof(param_id_drc_config_t)) ? CAPI_ENEEDMORE : result; + CHECK_THROW_ERROR(me_ptr->miid, + result, + "Insufficient size for drc config parameter. Received %lu bytes", + params_ptr->max_data_len); + + param_id_drc_config_t *drc_params_ptr = (param_id_drc_config_t *)params_ptr->data_ptr; + drc_config_t drc_config = { 0 }; + drc_mode_t drc_mode = DRC_BYPASSED; + memset(drc_params_ptr, 0, sizeof(param_id_drc_config_t)); + + if (me_ptr->lib_handle.lib_mem_ptr) + { + uint32_t temp = 0; + DRC_RESULT lib_result = + drc_get_param(&me_ptr->lib_handle, DRC_PARAM_CONFIG, (int8_t *)&drc_config, sizeof(drc_config), &temp); + if (lib_result != DRC_SUCCESS) + { + DRC_MSG(me_ptr->miid, DBG_ERROR_PRIO, "drc config get param failed with error %lu", lib_result); + return CAPI_EFAILED; + } + + lib_result = + drc_get_param(&me_ptr->lib_handle, DRC_PARAM_FEATURE_MODE, (int8_t *)&drc_mode, sizeof(drc_mode), &temp); + if (lib_result != DRC_SUCCESS) + { + DRC_MSG(me_ptr->miid, DBG_ERROR_PRIO, "drc mode get param failed with error %lu", lib_result); + return CAPI_EFAILED; + } + } + else + { + memscpy(&drc_config, sizeof(drc_config), &me_ptr->lib_cfg, sizeof(me_ptr->lib_cfg)); + drc_mode = me_ptr->mode; + } + + drc_params_ptr->delay_us = me_ptr->delay_us; + drc_params_ptr->mode = drc_mode; + drc_params_ptr->channel_linked_mode = drc_config.channelLinked; + drc_params_ptr->down_sample_level = drc_config.downSampleLevel; + drc_params_ptr->down_cmpsr_attack = drc_config.dnCompAttackUL32Q31; + drc_params_ptr->down_cmpsr_hysteresis = drc_config.dnCompHysterisisUL16Q14; + drc_params_ptr->down_cmpsr_release = drc_config.dnCompReleaseUL32Q31; + drc_params_ptr->down_cmpsr_slope = drc_config.dnCompSlopeUL16Q16; + drc_params_ptr->down_cmpsr_threshold = drc_config.dnCompThresholdL16Q7; + drc_params_ptr->down_expdr_attack = drc_config.dnExpaAttackUL32Q31; + drc_params_ptr->down_expdr_hysteresis = drc_config.dnExpaHysterisisUL16Q14; + drc_params_ptr->down_expdr_min_gain_db = drc_config.dnExpaMinGainDBL32Q23; + drc_params_ptr->down_expdr_release = drc_config.dnExpaReleaseUL32Q31; + drc_params_ptr->down_expdr_slope = drc_config.dnExpaSlopeL16Q8; + drc_params_ptr->down_expdr_threshold = drc_config.dnExpaThresholdL16Q7; + drc_params_ptr->makeup_gain = drc_config.makeupGainUL16Q12; + drc_params_ptr->rms_time_avg_const = drc_config.rmsTavUL16Q16; + drc_params_ptr->up_cmpsr_attack = drc_config.upCompAttackUL32Q31; + drc_params_ptr->up_cmpsr_hysteresis = drc_config.upCompHysterisisUL16Q14; + drc_params_ptr->up_cmpsr_release = drc_config.upCompReleaseUL32Q31; + drc_params_ptr->up_cmpsr_slope = drc_config.upCompSlopeUL16Q16; + drc_params_ptr->up_cmpsr_threshold = drc_config.upCompThresholdL16Q7; + + params_ptr->actual_data_len = sizeof(param_id_drc_config_t); + + break; + } + default: { + DRC_MSG(me_ptr->miid, DBG_ERROR_PRIO, "Invalid getparam received 0x%lx", param_id); + return CAPI_EUNSUPPORTED; + } + } + return result; +} + +static capi_err_t capi_set_properties(capi_t *_pif, capi_proplist_t *props_ptr) +{ + capi_err_t result = CAPI_EOK; + capi_err_t result2 = CAPI_EOK; + + result |= (!_pif || !props_ptr) ? CAPI_EFAILED : result; + CHECK_THROW_ERROR(MIID_UNKNOWN, result, "Error! set properties received null pointer."); + + capi_drc_t *me_ptr = (capi_drc_t *)_pif; + + result |= capi_cmn_set_basic_properties(props_ptr, (capi_heap_id_t *)&me_ptr->heap_id, &me_ptr->cb_info, TRUE); + + for (uint32_t i = 0; i < props_ptr->props_num; i++) + { + capi_prop_t *current_prop_ptr = &(props_ptr->prop_ptr[i]); + capi_buf_t * payload_ptr = &(current_prop_ptr->payload); + result2 = CAPI_EOK; + switch (current_prop_ptr->id) + { + case CAPI_EVENT_CALLBACK_INFO: + case CAPI_HEAP_ID: + case CAPI_PORT_NUM_INFO: { + continue; + } + case CAPI_ALGORITHMIC_RESET: { + // Call drc algorithm reset here + if (me_ptr->lib_handle.lib_mem_ptr) + { + drc_set_param(&me_ptr->lib_handle, DRC_PARAM_SET_RESET, NULL, 0); + } + } + break; + case CAPI_INPUT_MEDIA_FORMAT_V2: { + // Payload can be no smaller than the header for media format + if (payload_ptr->actual_data_len < CAPI_MF_V2_MIN_SIZE) + { + DRC_MSG(me_ptr->miid, DBG_ERROR_PRIO, "wrong size for input media format property."); + CAPI_SET_ERROR(result2, CAPI_EBADPARAM); + break; + } + + capi_media_fmt_v2_t *fmt_ptr = (capi_media_fmt_v2_t *)payload_ptr->data_ptr; + + DRC_MSG(me_ptr->miid, + DBG_LOW_PRIO, + "Received input media format, sampling_rate(%ld), " + "num_channels(%ld), bit_width(%ld)", + fmt_ptr->format.sampling_rate, + fmt_ptr->format.num_channels, + fmt_ptr->format.bits_per_sample); + + result2 = + (CAPI_FIXED_POINT != fmt_ptr->header.format_header.data_format) || + ((0 == fmt_ptr->format.num_channels) || (CAPI_MAX_CHANNELS_V2 < fmt_ptr->format.num_channels)) || + ((16 != fmt_ptr->format.bits_per_sample) && (32 != fmt_ptr->format.bits_per_sample)) || + ((16 == fmt_ptr->format.bits_per_sample) && (PCM_Q_FACTOR_15 != fmt_ptr->format.q_factor)) || + ((32 == fmt_ptr->format.bits_per_sample) && (PCM_Q_FACTOR_27 != fmt_ptr->format.q_factor)) + ? CAPI_EBADPARAM + : CAPI_EOK; + + if (CAPI_FAILED(result2)) + { + DRC_MSG(me_ptr->miid, DBG_ERROR_PRIO, "Invalid input media format received."); + break; + } + + memscpy(&me_ptr->input_media_fmt, sizeof(me_ptr->input_media_fmt), fmt_ptr, payload_ptr->actual_data_len); + + // allocate drc memory based on the new static parameters + result2 = drc_lib_alloc_init(me_ptr); + + raise_kpps_delay_process_events(me_ptr); + + // raise output media fmt + capi_cmn_output_media_fmt_event_v2(&me_ptr->cb_info, fmt_ptr, FALSE, 0); + break; + } + case CAPI_MODULE_INSTANCE_ID: { + if (payload_ptr->actual_data_len >= sizeof(capi_module_instance_id_t)) + { + capi_module_instance_id_t *data_ptr = (capi_module_instance_id_t *)payload_ptr->data_ptr; + me_ptr->miid = data_ptr->module_instance_id; +#if 0 + DRC_MSG(me_ptr->miid, + DBG_LOW_PRIO, + "CAPI DRC: This module-id 0x%08lX, instance-id 0x%08lX", + data_ptr->module_id, + me_ptr->miid); +#endif + } + else + { + DRC_MSG(MIID_UNKNOWN, + DBG_ERROR_PRIO, + "CAPI DRC: Set, Param id 0x%lx Bad param size %lu", + (uint32_t)current_prop_ptr->id, + payload_ptr->actual_data_len); + CAPI_SET_ERROR(result2, CAPI_ENEEDMORE); + } + break; + } + default: { + DRC_MSG(me_ptr->miid, DBG_HIGH_PRIO, "Invalid set property 0x%x.", (int)current_prop_ptr->id); + CAPI_SET_ERROR(result2, CAPI_EUNSUPPORTED); + break; + } + } + + if (CAPI_FAILED(result2)) + { + CAPI_SET_ERROR(result, result2); + } + } + return result; +} + +static capi_err_t capi_get_properties(capi_t *_pif, capi_proplist_t *props_ptr) +{ + capi_err_t result = CAPI_EOK; + capi_err_t result2 = CAPI_EOK; + capi_drc_t *me_ptr = (capi_drc_t *)_pif; + uint32_t miid = me_ptr ? me_ptr->miid : MIID_UNKNOWN; + result |= (props_ptr == NULL || props_ptr->prop_ptr == NULL) ? CAPI_EBADPARAM : result; + CHECK_THROW_ERROR(miid, result, "Bad Pointer received, Get property failed."); + + capi_prop_t * prop_ptr = props_ptr->prop_ptr; + uint32_t i; + capi_basic_prop_t mod_prop_ptr = { .init_memory_req = sizeof(capi_drc_t), + .stack_size = 1024, + .num_fwk_extns = 0, + .fwk_extn_ids_arr = NULL, + .is_inplace = TRUE, + .req_data_buffering = FALSE, + .max_metadata_size = 0 }; + + result |= capi_cmn_get_basic_properties(props_ptr, &mod_prop_ptr); + + // iterating over the properties + for (i = 0; i < props_ptr->props_num; i++) + { + capi_buf_t *payload_ptr = &prop_ptr[i].payload; + + switch (prop_ptr[i].id) + { + case CAPI_INIT_MEMORY_REQUIREMENT: + case CAPI_STACK_SIZE: + case CAPI_IS_INPLACE: + case CAPI_REQUIRES_DATA_BUFFERING: + case CAPI_NUM_NEEDED_FRAMEWORK_EXTENSIONS: + case CAPI_NEEDED_FRAMEWORK_EXTENSIONS: + case CAPI_MAX_METADATA_SIZE: { + // handled in capi common utils. + break; + } + case CAPI_OUTPUT_MEDIA_FORMAT_SIZE: { + if (NULL == me_ptr) + { + DRC_MSG(miid, DBG_ERROR_PRIO, "Get property id 0x%lx, module is not allocated", prop_ptr[i].id); + CAPI_SET_ERROR(result, CAPI_EBADPARAM); + break; + } + + result2 |= + (payload_ptr->max_data_len < sizeof(capi_output_media_format_size_t)) ? CAPI_ENEEDMORE : CAPI_EOK; + if (CAPI_FAILED(result2)) + { + payload_ptr->actual_data_len = 0; + DRC_MSG(miid, DBG_ERROR_PRIO, "Insufficient get property size."); + CAPI_SET_ERROR(result, result2); + break; + } + + capi_output_media_format_size_t *data_ptr = (capi_output_media_format_size_t *)payload_ptr->data_ptr; + + // One channel + data_ptr->size_in_bytes = sizeof(capi_standard_data_format_v2_t) + + me_ptr->input_media_fmt.format.num_channels * sizeof(capi_channel_type_t); + + payload_ptr->actual_data_len = sizeof(capi_output_media_format_size_t); + + break; + } + case CAPI_OUTPUT_MEDIA_FORMAT_V2: { + if (NULL == me_ptr) + { + DRC_MSG(miid, DBG_ERROR_PRIO, "Get property id 0x%lx, module is not allocated", prop_ptr[i].id); + CAPI_SET_ERROR(result, CAPI_EBADPARAM); + break; + } + + // One Channel + uint32_t required_size = sizeof(capi_set_get_media_format_t) + sizeof(capi_standard_data_format_v2_t) + + me_ptr->input_media_fmt.format.num_channels * sizeof(capi_channel_type_t); + + result2 |= (payload_ptr->max_data_len < required_size) ? CAPI_ENEEDMORE : CAPI_EOK; + if (CAPI_FAILED(result2)) + { + payload_ptr->actual_data_len = 0; + DRC_MSG(miid, DBG_ERROR_PRIO, "Insufficient get property size."); + CAPI_SET_ERROR(result, result2); + break; + } + + capi_media_fmt_v2_t *data_ptr = (capi_media_fmt_v2_t *)payload_ptr->data_ptr; + + payload_ptr->actual_data_len = + memscpy(data_ptr, required_size, &me_ptr->input_media_fmt, sizeof(me_ptr->input_media_fmt)); + + break; + } + + case CAPI_INTERFACE_EXTENSIONS: { + capi_interface_extns_list_t *intf_ext_list = (capi_interface_extns_list_t *)payload_ptr->data_ptr; + result2 |= + ((payload_ptr->max_data_len < sizeof(capi_interface_extns_list_t)) || + (payload_ptr->max_data_len < (sizeof(capi_interface_extns_list_t) + + (intf_ext_list->num_extensions * sizeof(capi_interface_extn_desc_t))))) + ? CAPI_ENEEDMORE + : result; + + if (CAPI_FAILED(result2)) + { + payload_ptr->actual_data_len = 0; + DRC_MSG(miid, DBG_ERROR_PRIO, "Insufficient get property size."); + CAPI_SET_ERROR(result, result2); + break; + } + + capi_interface_extn_desc_t *curr_intf_extn_desc_ptr = + (capi_interface_extn_desc_t *)(payload_ptr->data_ptr + sizeof(capi_interface_extns_list_t)); + + for (uint32_t j = 0; j < intf_ext_list->num_extensions; j++) + { + switch (curr_intf_extn_desc_ptr->id) + { + case INTF_EXTN_IMCL: + curr_intf_extn_desc_ptr->is_supported = TRUE; + break; + default: { + curr_intf_extn_desc_ptr->is_supported = FALSE; + break; + } + } + curr_intf_extn_desc_ptr++; + } + + break; + } + + default: { + payload_ptr->actual_data_len = 0; + DRC_MSG(miid, DBG_ERROR_PRIO, "Unsupported getproperty 0x%x.", prop_ptr[i].id); + result |= CAPI_EUNSUPPORTED; + break; + } + } // switch + } + return result; +} + +static capi_err_t capi_process(capi_t *_pif, capi_stream_data_t *input[], capi_stream_data_t *output[]) +{ + capi_err_t result = CAPI_EOK; + DRC_RESULT lib_result = DRC_SUCCESS; + int8_t * in_ptr[CAPI_MAX_CHANNELS_V2] = { NULL }; + int8_t * out_ptr[CAPI_MAX_CHANNELS_V2] = { NULL }; + + uint32_t samples_to_process = 0, bytes_to_process = 0, shift_factor = 0; + + if ((NULL == _pif) || (NULL == input) || (NULL == output)) + { + AR_MSG(DBG_ERROR_PRIO, "VCP:ACP: drc_process: received bad pointer"); + return CAPI_EFAILED; + } + capi_drc_t *me_ptr = (capi_drc_t *)_pif; + + // populate input buffers, output buffers and framesize + for (uint32_t i = 0; i < me_ptr->lib_static_cfg.num_channel && i < CAPI_MAX_CHANNELS_V2; i++) + { + in_ptr[i] = (int8_t *)(input[0]->buf_ptr[i].data_ptr); + out_ptr[i] = (int8_t *)(output[0]->buf_ptr[i].data_ptr); + } + bytes_to_process = (input[0]->buf_ptr[0].actual_data_len > output[0]->buf_ptr[0].max_data_len) + ? output[0]->buf_ptr[0].max_data_len + : input[0]->buf_ptr[0].actual_data_len; + shift_factor = (me_ptr->lib_static_cfg.data_width == BITS_16) ? 1 : 2; + samples_to_process = (bytes_to_process >> shift_factor); + + if (0 < samples_to_process) + { + lib_result = drc_process(&me_ptr->lib_handle, out_ptr, in_ptr, samples_to_process); + if (DRC_SUCCESS != lib_result) + { + DRC_MSG(me_ptr->miid, DBG_ERROR_PRIO, "process failed with result %lu", lib_result); + result = CAPI_EFAILED; + } + } + + for (uint32_t i = 0; i < me_ptr->lib_static_cfg.num_channel && i < CAPI_MAX_CHANNELS_V2; i++) + { + input[0]->buf_ptr[i].actual_data_len = samples_to_process << shift_factor; + output[0]->buf_ptr[i].actual_data_len = samples_to_process << shift_factor; + } + + output[0]->flags = input[0]->flags; + if (input[0]->flags.is_timestamp_valid) + { + output[0]->timestamp = input[0]->timestamp - me_ptr->delay_us; + } + + return result; +} + +static capi_err_t capi_end(capi_t *_pif) +{ + capi_err_t result = CAPI_EOK; + capi_drc_t *me_ptr = (capi_drc_t *)_pif; + + result |= (NULL == _pif) ? CAPI_EFAILED : result; + CHECK_THROW_ERROR(MIID_UNKNOWN, result, "end received null pointer."); + + uint32_t miid = me_ptr->miid; + + capi_cmn_ctrl_port_list_deinit(&me_ptr->ctrl_port_info); + + if (me_ptr->lib_handle.lib_mem_ptr) + { + posal_memory_free(me_ptr->lib_handle.lib_mem_ptr); + } + + me_ptr->vtbl = NULL; + + DRC_MSG(miid, DBG_HIGH_PRIO, "end."); + + return CAPI_EOK; +} diff --git a/modules/processing/gain_control/drc/capi/src/capi_drc_utils.cpp b/modules/processing/gain_control/drc/capi/src/capi_drc_utils.cpp new file mode 100644 index 0000000..776d004 --- /dev/null +++ b/modules/processing/gain_control/drc/capi/src/capi_drc_utils.cpp @@ -0,0 +1,283 @@ +/** +@file capi_drc_utils.cpp + +@brief CAPI V2 utility for DRC module. + +*/ + +/*----------------------------------------------------------------------- +Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. +SPDX-License-Identifier: BSD-3-Clause +-----------------------------------------------------------------------*/ + +#include "capi_drc_utils.h" + +// drc default parameters +static const drc_config_t drc_cfg_nb_t = { + CHANNEL_NOT_LINKED, // int16 stereoLinked + 0x1, // int16 downSampleLevel + 0x06F2, // uint16 rmsTavUL16Q16 + 0x1000, // uint16 makeupGainUL16Q12 + 0x0A28, // int16 dnExpaThresholdL16Q7 + (int16_t)0xFF9A, // int16 dnExpaSlopeL16Q8 + 0x05828860, // uint32 dnExpaAttackUL32Q31 + 0x0D554E9B, // uint32 dnExpaReleaseUL32Q31 + (int32_t)0xFD000000, // int32 dnExpaMinGainDBL32Q23 + 0x4934, // uint16 dnExpaHysterisisUL16Q14 + 0x0A28, // int16 upCompThresholdL16Q7 + 0x02C90623, // uint32 upCompAttackUL32Q31 + 0x02C90623, // uint32 upCompReleaseUL32Q31 + 0x0, // uint16 upCompSlopeUL16Q16 + 0x4934, // uint16 upCompHysterisisUL16Q14 + 0x1BA8, // int16 dnCompThresholdL16Q7 + 0xF333, // uint16 dnCompSlopeUL16Q16 + 0x19471064, // uint32 dnCompAttackUL32Q31 + 0x02C90623, // uint32 dnCompReleaseUL32Q31 + 0x4934 // uint16 dnCompHysterisisUL16Q14 +}; + +void drc_lib_set_default_config(capi_drc_t *me_ptr) +{ + // Initialize drc static parameters with default values + me_ptr->lib_static_cfg.data_width = BITS_16; + me_ptr->lib_static_cfg.num_channel = 1; + me_ptr->lib_static_cfg.sample_rate = 8000; + me_ptr->delay_us = 5000; // 5milleseconds. + + // default initializations + me_ptr->b_enable = 0; // By default DRC module disabled + me_ptr->mode = DRC_ENABLED; // DRC processing enabled; normal DRC processing + + memscpy(&(me_ptr->lib_cfg), sizeof(drc_config_t), &drc_cfg_nb_t, sizeof(drc_cfg_nb_t)); +} + +capi_err_t drc_lib_set_calib(capi_drc_t *me_ptr) +{ + capi_err_t result = CAPI_EOK; + DRC_RESULT lib_result = DRC_SUCCESS; + + result |= ((NULL == me_ptr) || (NULL == me_ptr->lib_handle.lib_mem_ptr)) ? CAPI_EFAILED : result; + CHECK_THROW_ERROR(MIID_UNKNOWN, result, "lib uninitialized.") + + lib_result = + drc_set_param(&me_ptr->lib_handle, DRC_PARAM_FEATURE_MODE, (int8_t *)&(me_ptr->mode), sizeof(drc_feature_mode_t)); + if (DRC_SUCCESS != lib_result) + { + DRC_MSG(me_ptr->miid, DBG_ERROR_PRIO, "lib feature mode set param failed. result %lu", lib_result); + return CAPI_EFAILED; + } + + lib_result = + drc_set_param(&me_ptr->lib_handle, DRC_PARAM_CONFIG, (int8_t *)&(me_ptr->lib_cfg), sizeof(drc_config_t)); + if (DRC_SUCCESS != lib_result) + { + DRC_MSG(me_ptr->miid, DBG_ERROR_PRIO, "lib config set param failed. result %lu", lib_result); + return CAPI_EFAILED; + } + + return result; +} + +capi_err_t drc_lib_alloc_init(capi_drc_t *me_ptr) +{ + capi_err_t result = CAPI_EOK; + DRC_RESULT lib_result = DRC_SUCCESS; + uint64_t sample_delay = 0; + + result |= (NULL == me_ptr) ? CAPI_EFAILED : result; + CHECK_THROW_ERROR(MIID_UNKNOWN, result, "null pointer.") + + if (me_ptr->input_media_fmt.format.sampling_rate == 0) + { + return CAPI_EOK; + } + + sample_delay = ((uint64_t)me_ptr->delay_us * (uint64_t)me_ptr->input_media_fmt.format.sampling_rate) / 1000000; + + me_ptr->lib_static_cfg.num_channel = me_ptr->input_media_fmt.format.num_channels; + me_ptr->lib_static_cfg.sample_rate = me_ptr->input_media_fmt.format.sampling_rate; + me_ptr->lib_static_cfg.data_width = (16 == me_ptr->input_media_fmt.format.bits_per_sample) ? BITS_16 : BITS_32; + me_ptr->lib_static_cfg.delay = sample_delay; + + // cache allocated memory size + uint32_t old_mem_size = me_ptr->mem_req.lib_mem_size; + + // Query memory requirement from drc library for new static parameters + lib_result = drc_get_mem_req(&me_ptr->mem_req, &me_ptr->lib_static_cfg); + if (DRC_SUCCESS != lib_result) + { + DRC_MSG(me_ptr->miid, DBG_ERROR_PRIO, "drc_get_mem_req failed, result 0x%lu", lib_result); + return CAPI_EFAILED; + } + + if (old_mem_size != me_ptr->mem_req.lib_mem_size) + { + // free current memory + if (NULL != me_ptr->lib_handle.lib_mem_ptr) + { + posal_memory_free(me_ptr->lib_handle.lib_mem_ptr); + me_ptr->lib_handle.lib_mem_ptr = NULL; + } + // allocate new memory + int8_t *lib_mem_ptr = (int8_t *)posal_memory_malloc(me_ptr->mem_req.lib_mem_size, me_ptr->heap_id); + if (NULL == lib_mem_ptr) + { + me_ptr->mem_req.lib_mem_size = 0; + CHECK_THROW_ERROR(me_ptr->miid, + CAPI_ENOMEMORY, + "memory allocation failed. bytes %lu", + me_ptr->mem_req.lib_mem_size); + } + + // Initialize drc library pointers + lib_result = + drc_init_memory(&me_ptr->lib_handle, &me_ptr->lib_static_cfg, lib_mem_ptr, me_ptr->mem_req.lib_mem_size); + + if (DRC_SUCCESS != lib_result) + { + DRC_MSG(me_ptr->miid, DBG_ERROR_PRIO, "drc_init_memory failed, result 0x%x", lib_result); + + posal_memory_free(lib_mem_ptr); + me_ptr->lib_handle.lib_mem_ptr = NULL; + me_ptr->mem_req.lib_mem_size = 0; + return CAPI_EFAILED; + } + + DRC_MSG(me_ptr->miid, DBG_LOW_PRIO, "drc lib init done."); + + // Initialize DRC calibration parameters with default/cached values. + result = drc_lib_set_calib(me_ptr); + } + return result; +} + +static uint32_t drc_get_kpps(capi_drc_t *me_ptr) +{ + uint32_t kpps = 0; + + uint32_t sample_rate = me_ptr->lib_static_cfg.sample_rate; + uint32_t num_channels = me_ptr->lib_static_cfg.num_channel; + uint32_t linked_mode = me_ptr->lib_cfg.channelLinked; + uint32_t downsample_level = me_ptr->lib_cfg.downSampleLevel; + + if (me_ptr->mode == DRC_BYPASSED) + { + // 3 packets per sample. + kpps = (sample_rate * num_channels * 3) / 1000; + } + else + { + /* + * Linked Mode: + * kpps = (channels * 0.01 + (0.04/downsample_level))*sample_rate; + * = (sample_rate*channels*1 + 4*sample_rate/downsample_level) / 100; + * + * Un Linked Mode: + * kpps = channels * (0.01 + (0.04/downsample_level)) *sample_rate; + * = (sample_rate*channels*1 + 4*sample_rate*channels/downsample_level) / + * 100; + */ + if (linked_mode) + { + kpps = (sample_rate * num_channels + (4 * sample_rate) / downsample_level) / 100; + } + else + { + kpps = (sample_rate * num_channels + (4 * sample_rate * num_channels) / downsample_level) / 100; + } + } + return kpps; +} + +static uint32_t drc_get_bw(capi_drc_t *me_ptr) +{ + uint32_t bw = 0; + + uint32_t sample_rate = me_ptr->lib_static_cfg.sample_rate; + uint32_t num_channels = me_ptr->lib_static_cfg.num_channel; + + if (me_ptr->mode == DRC_BYPASSED) + { + bw = 2 * sample_rate * num_channels; + } + else + { + bw = 10 * sample_rate * num_channels; + } + return bw; +} + +void raise_kpps_delay_process_events(capi_drc_t *me_ptr) +{ + if (me_ptr->b_enable) + { + uint32_t new_kpps = 0, new_bw = 0; + + new_kpps = drc_get_kpps(me_ptr); + + new_bw = drc_get_bw(me_ptr); + + // raise kpps event + capi_cmn_update_kpps_event(&me_ptr->cb_info, new_kpps); + + // raise algo delay event + capi_cmn_update_algo_delay_event(&me_ptr->cb_info, me_ptr->delay_us); + + capi_cmn_update_bandwidth_event(&me_ptr->cb_info, 0, new_bw); + } + + // raise process check event + capi_cmn_update_process_check_event(&me_ptr->cb_info, + ((me_ptr->b_enable) && (NULL != me_ptr->lib_handle.lib_mem_ptr)) ? TRUE : FALSE); +} + +capi_err_t drc_lib_send_config_imcl(capi_drc_t *me_ptr) +{ + capi_err_t result = CAPI_EOK; + uint32_t control_port_id = 0; + ctrl_port_data_t *port_data_ptr = NULL; + capi_buf_t buf; + buf.actual_data_len = sizeof(imc_param_header_t) + sizeof(param_id_imcl_drc_down_comp_threshold_t); + buf.data_ptr = NULL; + buf.max_data_len = buf.actual_data_len; + imcl_outgoing_data_flag_t flags; + flags.should_send = TRUE; + flags.is_trigger = FALSE; + + // Get the control port id for the intent #INTENT_ID_AVC + capi_cmn_ctrl_port_list_get_next_port_data(&me_ptr->ctrl_port_info, + INTENT_ID_DRC_CONFIG, + control_port_id, // initially, an invalid port id + &port_data_ptr); + + if (port_data_ptr == NULL || port_data_ptr->state != CTRL_PORT_PEER_CONNECTED) + { + return CAPI_EOK; + } + + control_port_id = port_data_ptr->port_info.port_id; + + // Get one time buf from the queue. + result |= capi_cmn_imcl_get_one_time_buf(&me_ptr->cb_info, control_port_id, buf.actual_data_len, &buf); + + if (CAPI_FAILED(result) || (NULL == buf.data_ptr)) + { + CHECK_THROW_ERROR(me_ptr->miid, CAPI_EFAILED, "Getting one time imcl buffer failed"); + } + + imc_param_header_t *out_cfg_ptr = (imc_param_header_t *)buf.data_ptr; + + out_cfg_ptr->opcode = PARAM_ID_IMCL_DRC_DOWN_COMP_THRESHOLD; + out_cfg_ptr->actual_data_len = sizeof(param_id_imcl_drc_down_comp_threshold_t); + + param_id_imcl_drc_down_comp_threshold_t *payload_ptr = + (param_id_imcl_drc_down_comp_threshold_t *)(buf.data_ptr + sizeof(imc_param_header_t)); + payload_ptr->drc_rx_enable = (me_ptr->b_enable) ? TRUE : FALSE; + payload_ptr->down_comp_threshold_L16Q7 = me_ptr->lib_cfg.dnCompThresholdL16Q7; + + // Send data ready to the peer module + result = capi_cmn_imcl_send_to_peer(&me_ptr->cb_info, &buf, control_port_id, flags); + CHECK_THROW_ERROR(me_ptr->miid, result, "failed in sending imcl buffer to peer module."); + + return result; +} diff --git a/modules/processing/gain_control/drc/capi/src/capi_drc_utils.h b/modules/processing/gain_control/drc/capi/src/capi_drc_utils.h new file mode 100644 index 0000000..604a084 --- /dev/null +++ b/modules/processing/gain_control/drc/capi/src/capi_drc_utils.h @@ -0,0 +1,77 @@ +/** +@file capi_drc_utils.h + +@brief CAPI V2 utility header for DRC module. + +*/ + +/*----------------------------------------------------------------------- +Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. +SPDX-License-Identifier: BSD-3-Clause +-----------------------------------------------------------------------*/ + +#include "api_drc.h" +#include "capi_cmn.h" +#include "capi_cmn_ctrl_port_list.h" +#include "capi_drc.h" +#include "drc_api.h" //lib interface +#include "imcl_drc_info_api.h" + +/*------------------------------------------------------------------------------ + * Defines + *-----------------------------------------------------------------------------*/ + +#define MIID_UNKNOWN 0 + +#define DRC_MSG_PREFIX "DRC:[%lX] " +#define DRC_MSG(ID, xx_ss_mask, xx_fmt, ...) AR_MSG(xx_ss_mask, DRC_MSG_PREFIX xx_fmt, ID, ##__VA_ARGS__) + +#define CHECK_THROW_ERROR(ID, result, error_msg, ...) \ + { \ + if (CAPI_FAILED(result)) \ + { \ + DRC_MSG(ID, DBG_ERROR_PRIO, error_msg, ##__VA_ARGS__); \ + return result; \ + } \ + } + +/* KPPS values */ +#define DRC_NB_KPPS (500) // 0.5 mpps in kpps for nb +#define DRC_WB_KPPS (800) // 0.8 mpps in kpps for wb +#define DRC_SWB_KPPS (2000) // 2 mpps in kpps for swb - approx +#define DRC_FB_KPPS (2730) // 2.71 mpps in kpps for fb - approx. + +/*------------------------------------------------------------------------------ + * Type Definition + *-----------------------------------------------------------------------------*/ + +typedef struct capi_drc_t +{ + capi_vtbl_t * vtbl; + capi_event_callback_info_t cb_info; + POSAL_HEAP_ID heap_id; + + drc_lib_t lib_handle; // Library instance + drc_static_struct_t lib_static_cfg; // static parameters structure + drc_config_t lib_cfg; // Configuration structure + drc_feature_mode_t mode; // operation mode + drc_lib_mem_requirements_t mem_req; // memory requirements structure + int16_t b_enable; // enable/disable lib + uint32_t delay_us; // drc delay in microseconds. + uint32_t miid; // Module Instance ID + + ctrl_port_list_handle_t ctrl_port_info; + + capi_media_fmt_v2_t input_media_fmt; +} capi_drc_t; + +/*------------------------------------------------------------------------------ + * Function Declarations + *-----------------------------------------------------------------------------*/ +void drc_lib_set_default_config(capi_drc_t *me_ptr); +capi_err_t drc_lib_set_calib(capi_drc_t *me_ptr); +capi_err_t drc_lib_alloc_init(capi_drc_t *me_ptr); + +capi_err_t drc_lib_send_config_imcl(capi_drc_t *me_ptr); + +void raise_kpps_delay_process_events(capi_drc_t *me_ptr); diff --git a/modules/processing/gain_control/drc/lib/inc/CDrcLib.h b/modules/processing/gain_control/drc/lib/inc/CDrcLib.h new file mode 100644 index 0000000..87d8639 --- /dev/null +++ b/modules/processing/gain_control/drc/lib/inc/CDrcLib.h @@ -0,0 +1,289 @@ +#ifndef DRCLIB_H +#define DRCLIB_H +/*============================================================================ + @file CDrcLib.h + + Public header file for the Limiter algorithm. + + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause +============================================================================*/ + +/*---------------------------------------------------------------------------- + * Include Files + * -------------------------------------------------------------------------*/ + +#include + +#include "AudioComdef.h" +#include "audpp_common.h" + +#ifndef __GNUC__ +#define __attribute__(arg) /* noop */ +#endif + + +static const int16 MAX_DELAY_SAMPLE = 1201; +static const uint16 ONE_OVER_TWENTY_UQ19 = 26214; +static const uint32 DRC_BLOCKSIZE = 80; /* native DRC frame size */ +static const int32 MIN_RMS_DB_L32Q23 = 0; +static const int16 MIN_RMS_DB_L16Q7 = -728; +static const int32 DB_16384_L32Q23 = 707051520; +static const int16 MAKEUPGAIN_UNITY = 4096; + + +/*---------------------------------------------------------------------------- + * Type Declarations + * -------------------------------------------------------------------------*/ + +/** + @brief Data types used in the DRC library + */ +typedef enum +{ + DRC_DISABLED = 0, + DRC_ENABLED = 1 + +} DrcFeaturesType; + +typedef enum +{ + CHANNEL_NOT_LINKED = 0, + CHANNEL_LINKED = 1 + +} DrcStereoLinkedType; + +typedef enum +{ + NO_CHANGE = 0, + ATTACK = 1, + RELEASE = 2 + +} DrcStateType; + +/** + @brief Default values of tuning parameters and hardcoded parameters used in the algorithm + */ +typedef enum +{ + // Default parameters + NUM_CHANNEL_DEFAULT = 1, + STEREO_LINKED_DEFAULT = 1, + + MODE_DEFAULT = 1, + DOWNSAMPLE_LEVEL_DEFAULT = 1, + DELAY_DEFAULT = 800, + RMS_TAV_DEFAULT = 4, + MAKEUP_GAIN_DEFAULT = 4650, + + DN_EXPA_THRESHOLD_DEFAULT = 0, + DN_EXPA_SLOPE_DEFAULT = 1, + DN_EXPA_ATTACK_DEFAULT = 2, + DN_EXPA_RELEASE_DEFAULT = 3, + DN_EXPA_HYSTERISIS_DEFAULT = 10023, + DN_EXPA_MIN_GAIN_DEFAULT = 32767, + + UP_COMP_THRESHOLD_DEFAULT = 4, + UP_COMP_SLOPE_DEFAULT = 5, + UP_COMP_ATTACK_DEFAULT = 6, + UP_COMP_RELEASE_DEFAULT = 7, + UP_COMP_HYSTERISIS_DEFAULT = 10023, + + DN_COMP_THRESHOLD_DEFAULT = 8, + DN_COMP_SLOPE_DEFAULT = 9, + DN_COMP_ATTACK_DEFAULT = 10, + DN_COMP_RELEASE_DEFAULT = 11, + DN_COMP_HYSTERISIS_DEFAULT = 10023 + +} DrcParamsDefault; + + + + +/*---------------------------------------------------------------------------- + * Type Declarations for overall configuration and state data structures + * -------------------------------------------------------------------------*/ +/** + @brief DRC configuration parameter structure containing tuning parameters + this structure is used with the Initialize/ReInitial functions, it is part of the + library interface, the structure is used by external caller to this library. + */ + +typedef struct _DrcConfig +{ + // DrcMiscCfg + int16 numChannel; // Q0 Number of channels - Internally set value + int16 stereoLinked; // Q0 stereo mode -- Linked or Not-Linked + + int16 mode; // Q0 DRC mode code - DRC_DISABLED or DRC_ENABLED + int16 downSampleLevel; // Q0 Down Sample Level to save MIPS + int16 delay; // Q0 Delay in samples + uint16 rmsTavUL16Q16; // Q16 Time Constant used to compute Input RMS + uint16 makeupGainUL16Q12; // Q12 Makeup Gain Value - [258, 64918] absolute + + // DrcStaticCurveCfg + int16 dnExpaThresholdL16Q7; + int16 dnExpaSlopeL16Q8; + uint32 dnExpaAttackUL32Q31; + uint32 dnExpaReleaseUL32Q31; + uint16 dnExpaHysterisisUL16Q14; + int32 dnExpaMinGainDBL32Q23; + + int16 upCompThresholdL16Q7; + uint16 upCompSlopeUL16Q16; + uint32 upCompAttackUL32Q31; + uint32 upCompReleaseUL32Q31; + uint16 upCompHysterisisUL16Q14; + + int16 dnCompThresholdL16Q7; + uint16 dnCompSlopeUL16Q16; + uint32 dnCompAttackUL32Q31; + uint32 dnCompReleaseUL32Q31; + uint16 dnCompHysterisisUL16Q14; + +} DrcConfig; + +/** + @brief DRC configuration parameter structure containing tuning parameters + this structure is used with the ASM optimized code, for + library internal use. + */ +typedef struct _DrcConfigInternal +{ + // Don't change the order of the fields above the line which are used in ASM + uint16 rmsTavUL16Q16; // Q16 Time Constant used to compute Input RMS + uint16 negativeDrcTavUL16Q16; // for unsigned Q16, (1-TAV) equals to -TAV + uint16 makeupGainUL16Q12; // Q12 Makeup Gain Value - [258, 64918] absolute + int16 delay; // Q0 Delay in samples + int32 downSampleLevel; // Q0 Down Sample Level to save MIPS + + int32 dnCompThresholdL16Q7; + int32 dnCompThrMultSlopeL32Q23; + uint32 dnCompSlopeUL16Q16; + + int32 dnExpaSlopeL16Q8; + int32 dnExpaThresholdL16Q7; + int32 dnExpaNewTargetGainL32Q23; + int32 dnExpaMinGainDBL32Q23; + + int32 upCompThrMultSlopeL32Q23; + uint32 upCompSlopeUL16Q16; + int32 upCompThresholdL16Q7; +#if ((defined __hexagon__) || (defined __qdsp6__)) + int32 stereoLinked; +#else + DrcStereoLinkedType stereoLinked; // Q0 stereo mode -- Linked or Not-Linked +#endif + uint32 dnCompHysterisisUL16Q14Asl1; + int32 outDnCompThresholdL16Q7; + uint32 dnCompAttackUL32Q31; + uint32 dnCompReleaseUL32Q31; + + uint32 dnExpaHysterisisUL16Q14Asl1; + int32 outDnExpaThresholdL16Q7; + uint32 dnExpaAttackUL32Q31; + uint32 dnExpaReleaseUL32Q31; + + uint32 upCompHysterisisUL16Q14Asl1; + int32 outUpCompThresholdL16Q7; + uint32 upCompAttackUL32Q31; + uint32 upCompReleaseUL32Q31; + //------------------------------ + + int16 numChannel; // Q0 Number of channels - Internally set value + + DrcFeaturesType mode; // Q0 DRC mode code - DRC_DISABLED or DRC_ENABLED + + uint32 dnCompHysterisisUL16Q14; + uint32 dnExpaHysterisisUL16Q14; + uint32 upCompHysterisisUL16Q14; + +} DrcConfigInternal; + + +/** + @brief DRC processing data structure + */ +typedef struct _DrcData +{ + // Don't change the order of the fields which are used in ASM + int32 *paGainL32Q15[2]; + int16 *pDelayBufferLeftL16; + int16 *pDelayBufferRightL16; + int32 rmsStateL32[2]; + int32 *pRmsStateL32; + int32 downSampleCounter; +#if ((defined __hexagon__) || (defined __qdsp6__)) + uint32 currState[2]; +#else + DrcStateType currState[2]; +#endif + int32 gainL32Q15[2]; + uint32 timeConstantUL32Q31[2]; + int32 dwcomp_state_change[2]; + int32 uwcomp_state_change[2]; +} DrcData; + +/*---------------------------------------------------------------------------- + * Type Declarations for overall configuration and state data structures + * -------------------------------------------------------------------------*/ + +class CDrcLib { +private: + + /*---------------------------------------------------------------------------- + * Constants Definition + * -------------------------------------------------------------------------*/ + static const int16 MAX_INT16_DB_Q7 = 11559; + static const int32 ONE_Q23 = 8388608; + static const uint16 HALF_UL16_Q16 = 32768; + +private: + DrcConfigInternal m_drcCfgInt __attribute__ ((aligned (8))); + DrcData m_drcData __attribute__ ((aligned (8))); + + int32 m_aGainL32Q15[2][DRC_BLOCKSIZE] __attribute__ ((aligned (8))); + int32 m_aRmsStateL32[2*DRC_BLOCKSIZE] __attribute__ ((aligned (8))); + int16 m_delayBufferLeftL16[MAX_DELAY_SAMPLE+DRC_BLOCKSIZE] __attribute__ ((aligned (8))); + int16 m_delayBufferRightL16[MAX_DELAY_SAMPLE+DRC_BLOCKSIZE] __attribute__ ((aligned (8))); + + void (*fnpProcess)(DrcConfigInternal *pDrcCfgInt, DrcData *pDrcData, + int16 *pOutPtrL16, int16 *pOutPtrR16, + int16 *pInPtrL16, int16 *pInPtrR16, + uint32 nSampleCnt); + +public: + CDrcLib(); + + /** + @brief Process input audio data with DRC algorithm + + @param pInPtrL16: [in] Pointer to 16-bit Q15 input channel data + @param pInPtrR16: [in] Pointer to 16-bit Q15 input channel data + @param pOutPtrL16: [out] Pointer to 16-bit Q15 output channel data + @param pOutPtrR16: [out] Pointer to 16-bit Q15 output channel data + */ + void Process(int16 *pOutPtrL16, + int16 *pOutPtrR16, + int16 *pInPtrL16, + int16 *pInPtrR16, + uint32 nSampleCnt=DRC_BLOCKSIZE); + + /** + @brief Initialize DRC algorithm + + Performs initialization of data structures for the + DRC algorithm. Two pointers to two memory is passed for + configuring the DRC static configuration + structure. + + */ + PPStatus Initialize(DrcConfig &cfg); + + PPStatus ReInitialize(DrcConfig &cfg); + + void Reset(); +}; + + +#endif /* #ifndef DRCLIB_H */ diff --git a/modules/processing/gain_control/drc/lib/inc/drc_api.h b/modules/processing/gain_control/drc/lib/inc/drc_api.h new file mode 100644 index 0000000..b172db7 --- /dev/null +++ b/modules/processing/gain_control/drc/lib/inc/drc_api.h @@ -0,0 +1,126 @@ +#ifndef DRCAPI_H +#define DRCAPI_H +/*============================================================================ + @file CDrcApi.h + + Public api for DRC. + + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause +============================================================================*/ + +/*---------------------------------------------------------------------------- + * Include Files + * -------------------------------------------------------------------------*/ + +#include "drc_calib_api.h" + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + + +/*---------------------------------------------------------------------------- + * Type Declarations + * -------------------------------------------------------------------------*/ + + +typedef enum DRC_RESULT +{ + DRC_SUCCESS = 0, + DRC_FAILURE, + DRC_MEMERROR +}DRC_RESULT; + +typedef enum data_width_type +{ + BITS_16 = 0, // 16-bits sample + BITS_32 // 32-bits sample +} data_width_type; + + +//DRC static params structure +typedef struct drc_static_struct_t +{ + + data_width_type data_width; // 0(16-bits sample) or 1(32-bits sample) + uint32_t sample_rate; // Hz + uint32_t num_channel; // Q0 Number of channels + uint32_t delay; // Q0 Delay in samples per channel + +} drc_static_struct_t; + + + + + +// DRC lib structure +typedef struct drc_lib_t +{ + + void* lib_mem_ptr; // ptr to the total chunk of lib mem + +} drc_lib_t; + + + +// DRC lib mem requirements structure +typedef struct drc_lib_mem_requirements_t +{ + + uint32_t lib_mem_size; // size of the lib mem pointed by lib_mem_ptr + uint32_t lib_stack_size; // stack mem size +} drc_lib_mem_requirements_t; + + +/*---------------------------------------------------------------------------- + * Function Declarations and Documentation + * -------------------------------------------------------------------------*/ + +// DRC processing of de-interleaved multi-channel input audio signal sample by sample +// drc_lib_ptr: [in] Pointer to lib structure +// out_ptr: [out] Pointer to de-interleaved multi - channel output PCM samples +// in_ptr: [in] Pointer to de-interleaved multi - channel input PCM samples +// sample_per_channel: [in] Number of samples to be processed per channel +DRC_RESULT drc_process(drc_lib_t *drc_lib_ptr, int8_t **out_ptr, int8_t **in_ptr, uint32_t sample_per_channel); + + +// get DRC lib mem size +// drc_lib_mem_requirements_ptr: [out] Pointer to lib mem requirements structure +// static_struct_ptr: [in] Pointer to static structure +DRC_RESULT drc_get_mem_req(drc_lib_mem_requirements_t *drc_lib_mem_requirements_ptr, drc_static_struct_t* drc_static_struct_ptr); + +// partition and init the mem with params +// drc_lib_ptr: [in, out] Pointer to lib structure +// static_struct_ptr: [in] Pointer to static structure +// mem_ptr: [in] Pointer to the lib memory +// mem_size: [in] Size of the memory pointed by mem_ptr +DRC_RESULT drc_init_memory(drc_lib_t *drc_lib_ptr, drc_static_struct_t *drc_static_struct_ptr, int8_t *mem_ptr, uint32_t mem_size); + +// set the params in the lib mem with those pointed by mem_ptr +// drc_lib_ptr: [in, out] Pointer to lib structure +// param_id: [in] ID of the param +// mem_ptr: [in] Pointer to the memory where the values stored are used to set up the params in the lib memory +// mem_size:[in] Size of the memory pointed by mem_ptr +DRC_RESULT drc_set_param(drc_lib_t *drc_lib_ptr, uint32_t param_id, int8_t *mem_ptr, uint32_t mem_size); + +// retrieve params from lib mem +// drc_lib_ptr: [in] Pointer to lib structure +// param_id: [in] ID of the param +// mem_ptr: [out] Pointer to the memory where params are to be stored +// mem_size:[in] Size of the memory pointed by mem_ptr +// param_size_ptr: [out] Pointer to param size which indicates the size of the retrieved param(s) +DRC_RESULT drc_get_param(drc_lib_t *drc_lib_ptr, uint32_t param_id, int8_t *mem_ptr, uint32_t mem_size, uint32_t *param_size_ptr); + + + + + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* #ifndef DRCAPI_H */ diff --git a/modules/processing/gain_control/drc/lib/inc/drc_calib_api.h b/modules/processing/gain_control/drc/lib/inc/drc_calib_api.h new file mode 100644 index 0000000..dadd9fd --- /dev/null +++ b/modules/processing/gain_control/drc/lib/inc/drc_calib_api.h @@ -0,0 +1,134 @@ +#ifndef DRC_CALIB_API_H +#define DRC_CALIB_API_H +/*============================================================================ + @file CDrcCalibApi.h + + Public api for DRC. + + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause +============================================================================*/ +/*============================================================================ + + $Header: //components/rel/audioreach_spm_pp.cmn/0.0/gain_control/drc/lib/inc/drc_calib_api.h#1 $ + + when who what, where, why + ---------- ------- --------------------------------------------------------- + 2012-11-12 juihuaj Initial revision. + 2012-11-28 juihuaj Added param IDs and corresponding structures +============================================================================*/ + +/*---------------------------------------------------------------------------- + * Include Files + * -------------------------------------------------------------------------*/ + + +#include "ar_defs.h" + + + + + + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + + + +/*---------------------------------------------------------------------------- + * Type Declarations + * -------------------------------------------------------------------------*/ +// param ID and the corresponding payload for lib version +#define DRC_PARAM_GET_LIB_VER (0) // read only +typedef int64_t drc_lib_ver_t; // lib version(major.minor.bug); (8bits.16bits.8bits) + + + + +typedef enum drc_mode_t +{ + DRC_BYPASSED = 0, // DRC processing bypassed; no DRC processing and only delay is implemented + DRC_ENABLED // DRC processing enabled; normal DRC processing + +} drc_mode_t; + +// param ID and the corresponding payload for DRC feature mode +#define DRC_PARAM_FEATURE_MODE (1) // read/write +typedef drc_mode_t drc_feature_mode_t; // 1 is with DRC processing; 0 is no DRC processing(bypassed, only delay is implemented) + + + +typedef enum drc_channel_linking_t +{ + CHANNEL_NOT_LINKED = 0, + CHANNEL_LINKED + +} drc_channel_linking_t; + +// param ID and the corresponding payload for DRC processing +#define DRC_PARAM_CONFIG (2) // read/write +typedef struct drc_config_t +{ + + // below two should not change during Reinit + int16_t channelLinked; // Q0 channel mode -- Linked(1) or Not-Linked(0) + int16_t downSampleLevel; // Q0 Down Sample Level to save MIPS + + + uint16_t rmsTavUL16Q16; // Q16 Time Constant used to compute Input RMS + uint16_t makeupGainUL16Q12; // Q12 Makeup Gain Value + + + int16_t dnExpaThresholdL16Q7; + int16_t dnExpaSlopeL16Q8; + uint32_t dnExpaAttackUL32Q31; + uint32_t dnExpaReleaseUL32Q31; + int32_t dnExpaMinGainDBL32Q23; + uint16_t dnExpaHysterisisUL16Q14; + + int16_t upCompThresholdL16Q7; + uint32_t upCompAttackUL32Q31; + uint32_t upCompReleaseUL32Q31; + uint16_t upCompSlopeUL16Q16; + uint16_t upCompHysterisisUL16Q14; + + int16_t dnCompThresholdL16Q7; + uint16_t dnCompSlopeUL16Q16; + uint32_t dnCompAttackUL32Q31; + uint32_t dnCompReleaseUL32Q31; + uint16_t dnCompHysterisisUL16Q14; + + + + int16_t dummy; // avoid memory hole + +} drc_config_t; + + +// param ID for reset(to flush memory) +// no payload needed for this ID +#define DRC_PARAM_SET_RESET (3) // write only + + + +// param ID and the corresponding payload for delay(in samples) +#define DRC_PARAM_GET_DELAY (4) // read only +typedef uint32_t drc_delay_t; // Q0 Delay in samples per channel + + + + + + + + + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* #ifndef DRC_CALIB_API_H */ diff --git a/modules/processing/gain_control/drc/lib/src/CDrcLib.cpp b/modules/processing/gain_control/drc/lib/src/CDrcLib.cpp new file mode 100644 index 0000000..f0cd114 --- /dev/null +++ b/modules/processing/gain_control/drc/lib/src/CDrcLib.cpp @@ -0,0 +1,1267 @@ +/*============================================================================ + FILE: CDrcLib.cpp + + OVERVIEW: Implements the drciter algorithm. + + DEPENDENCIES: None + +Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. +SPDX-License-Identifier: BSD-3-Clause +============================================================================*/ + +/*---------------------------------------------------------------------------- + * Include Files + * -------------------------------------------------------------------------*/ + +#include +#include +#include +#include "CDrcLib.h" +#include "audio_basic_op.h" +#include "audio_log10.h" +#include "audio_exp10.h" + + +#if ((defined __hexagon__) || (defined __qdsp6__)) + +#define s32_mult_s32_u16_shift16(var1,var2) (int32)Q6_P_vmpyweuh_PP_sat((int64)(var1),(uint64)(var2)) +#define s32_lsr_u32(var1,shift) Q6_R_lsr_RR((var1),(shift)) +#define s32_msu_s16_u16(var3,var1,var2) Q6_P_mpynac_RR((int64)(var3),(int32)(var1),(uint32)(var2)) +#define s32_mult_s32_s16_shift15_sat(var1,var2) (int32)Q6_P_vmpyweh_PP_s1_sat((int64)(var1),(uint64)(var2)) +#define s32_mult_s32_u16_shift15_sat(var1,var2) (int32)Q6_P_vmpyweuh_PP_s1_sat((int64)(var1),(uint64)(var2)) + +extern "C" { +int32 Exp10Fixed(int32 input); +int32 Log10Fixed(int32 input); +} + +#else + +#define s32_mult_s32_u16_shift16(var1,var2) (int32)s64_mult_s32_u16_shift((var1),(var2),0) +#define s32_lsr_u32(var1,shift) s32_saturate_s64(s64_shl_s64((var1),(-shift))) +#define s32_msu_s16_u16(var3,var1,var2) s32_sub_s32_s32((var3),s32_mult_s16_u16((var1),(var2))) +#define s32_mult_s32_s16_shift15_sat(var1,var2) s32_saturate_s64(s64_mult_s32_s16_shift(((var1)<<1),(var2),0)) +#define s32_mult_s32_u16_shift15_sat(var1,var2) s32_saturate_s64(s64_mult_s32_u16_shift((var1),(var2),1)) + +#define Exp10Fixed exp10_fixed +#define Log10Fixed log10_fixed + +#endif + + +#if ((defined __hexagon__) || (defined __qdsp6__)) + +extern "C" { +void ComputeInputRmsMono(DrcConfigInternal *pDrcCfg, DrcData *pDrcData, int16 *pInputL16, uint32 nSampleCnt); +} + +#else + +static void ComputeInputRmsMono(DrcConfigInternal *pDrcCfg, DrcData *pDrcData, int16 *pInputL16, uint32 nSampleCnt) +{ + uint32 i; + int16 nInputL; + int32 currDrcRmsLeftL32; + + for (i=0; irmsStateL32[0] = s32_add_s32_s32( + s32_mult_s32_u16_shift16(currDrcRmsLeftL32, pDrcCfg->rmsTavUL16Q16), + s32_mult_s32_u16_shift16(pDrcData->rmsStateL32[0], pDrcCfg->negativeDrcTavUL16Q16)); + + pDrcData->pRmsStateL32[i] = pDrcData->rmsStateL32[0]; + } +} + +#endif + + +#if ((defined __hexagon__) || (defined __qdsp6__)) + +extern "C" { +void ComputeInputRmsStereoLinked(DrcConfigInternal *pDrcCfg, DrcData *pDrcData, + int16 *pInputL16, int16 *pInputR16, uint32 nSampleCnt); +} + +#else + +static void ComputeInputRmsStereoLinked(DrcConfigInternal *pDrcCfg, DrcData *pDrcData, + int16 *pInputL16, int16 *pInputR16, uint32 nSampleCnt) +{ + uint32 i; + int16 nInputL, nInputR; + int32 currDrcRmsLeftL32; + int64 squareInputL64; + + for (i=0; irmsStateL32[0] = s32_add_s32_s32( + s32_mult_s32_u16_shift16(currDrcRmsLeftL32, pDrcCfg->rmsTavUL16Q16), + s32_mult_s32_u16_shift16(pDrcData->rmsStateL32[0], pDrcCfg->negativeDrcTavUL16Q16)); + + pDrcData->pRmsStateL32[i] = pDrcData->rmsStateL32[0]; + } +} + +#endif + + +#if ((defined __hexagon__) || (defined __qdsp6__)) + +extern "C" { +void ComputeInputRmsStereoUnLinked(DrcConfigInternal *pDrcCfg, DrcData *pDrcData, + int16 *pInputL16, int16 *pInputR16, uint32 nSampleCnt); +} + +#else + +static void ComputeInputRmsStereoUnLinked(DrcConfigInternal *pDrcCfg, DrcData *pDrcData, + int16 *pInputL16, int16 *pInputR16, uint32 nSampleCnt) +{ + uint32 i; + int16 nInputL, nInputR; + int32 currDrcRmsLeftL32, currDrcRmsRightL32; + + for (i=0; irmsStateL32[0] = s32_add_s32_s32( + s32_mult_s32_u16_shift16(currDrcRmsLeftL32, pDrcCfg->rmsTavUL16Q16), + s32_mult_s32_u16_shift16(pDrcData->rmsStateL32[0], pDrcCfg->negativeDrcTavUL16Q16)); + + // Right channel: drcRms = drcRms*(1-drcTav) + new_drcRms*drcTav + pDrcData->rmsStateL32[1] = s32_add_s32_s32( + s32_mult_s32_u16_shift16(currDrcRmsRightL32, pDrcCfg->rmsTavUL16Q16), + s32_mult_s32_u16_shift16(pDrcData->rmsStateL32[1], pDrcCfg->negativeDrcTavUL16Q16)); + + pDrcData->pRmsStateL32[2*i] = pDrcData->rmsStateL32[0]; + pDrcData->pRmsStateL32[2*i+1] = pDrcData->rmsStateL32[1]; + } +} + +#endif + + +#if ((defined __hexagon__) || (defined __qdsp6__)) + +extern "C" { +uint32 ComputeTargetGain(DrcConfigInternal *pDrcCfg, int16 drcRmsDBL16Q7); +} + +#else + +static uint32 ComputeTargetGain(DrcConfigInternal *pDrcCfg, int16 drcRmsDBL16Q7) +{ + uint16 tempSlopeUL16Q16; + int16 tempThresholdL16Q7, tempSlopeL16Q8; + int32 newTargetGainL32Q26, newTargetGainL32Q23, newTargetGainUL32Q15; + int64 tempRmsDBL40Q23; + int32 tempThrMultSlopeL32Q23; + + /* figure out what part of compression curve the rms value is in */ + if (drcRmsDBL16Q7 > pDrcCfg->dnCompThresholdL16Q7) { + // in Down Dompression + tempSlopeUL16Q16 = pDrcCfg->dnCompSlopeUL16Q16; + tempThrMultSlopeL32Q23 = pDrcCfg->dnCompThrMultSlopeL32Q23; + + // newTarget = dwCompThreshold * dwCompSlopeUL16Q16 - Xrms[n] * dwCompSlopeUL16Q16 + newTargetGainL32Q23 = s32_msu_s16_u16(tempThrMultSlopeL32Q23, drcRmsDBL16Q7, tempSlopeUL16Q16); + } + else if (drcRmsDBL16Q7 < pDrcCfg->dnExpaThresholdL16Q7) { + // in Down Expansion + // newTarget = (dwExpaThresholdL16Q7 - Xrms[n])*dwExpaSlopeUL16Q16 + ... + // (uwCompThresholdL16Q7 - dwExpaThresholdL16Q7)*uwCompSlopeUL16Q16; */ + + tempThresholdL16Q7 = pDrcCfg->dnExpaThresholdL16Q7; + tempSlopeL16Q8 = pDrcCfg->dnExpaSlopeL16Q8; +#if ((defined __hexagon__) || (defined __qdsp6__)) + tempRmsDBL40Q23 = s64_mult_s32_s32_shift(s32_sub_s32_s32(tempThresholdL16Q7, drcRmsDBL16Q7), + tempSlopeL16Q8, 40); +#else + tempRmsDBL40Q23 = s64_mult_s16_s16_shift(s32_sub_s32_s32(tempThresholdL16Q7, drcRmsDBL16Q7), + tempSlopeL16Q8, 8); +#endif + newTargetGainL32Q23 = s32_saturate_s64(s64_add_s64_s32(tempRmsDBL40Q23,pDrcCfg->dnExpaNewTargetGainL32Q23)); + /* L32Q23 */ + + //Limit the gain reduction in Downward Expander part to be dnExpaMinGainDB + newTargetGainL32Q23 = s32_max_s32_s32(newTargetGainL32Q23, pDrcCfg->dnExpaMinGainDBL32Q23); + } + else if (drcRmsDBL16Q7 < pDrcCfg->upCompThresholdL16Q7) { + // in Up Compression + tempSlopeUL16Q16 = pDrcCfg->upCompSlopeUL16Q16; + tempThrMultSlopeL32Q23 = pDrcCfg->upCompThrMultSlopeL32Q23; + + // newTarget = uwCompThreshold * uwCompSlopeUL16Q16 - Xrms[n] * uwCompSlopeUL16Q16 + newTargetGainL32Q23 = s32_msu_s16_u16(tempThrMultSlopeL32Q23, drcRmsDBL16Q7, tempSlopeUL16Q16); + } + else { + newTargetGainL32Q23 = 0x0000; + } + + /* calculate new target gain = 10^(new target gain log / 20): input L32Q26, out:L32Q15 */ + newTargetGainL32Q26 = s32_mult_s32_u16_shift16(newTargetGainL32Q23, ONE_OVER_TWENTY_UQ19); + newTargetGainUL32Q15 = Exp10Fixed(newTargetGainL32Q26); /* Q15 */ + + return (newTargetGainUL32Q15); +} + +#endif + + +#if ((defined __hexagon__) || (defined __qdsp6__)) + +extern "C" { +void CalculateDrcGainOneGain(DrcConfigInternal *pDrcCfg, DrcData *pDrcData, uint32 nSampleCnt); +} + +#else + +static void CalculateDrcGainOneGain(DrcConfigInternal *pDrcCfg, DrcData *pDrcData, uint32 nSampleCnt) +{ + uint32 i; + int64 currDrcGainL40Q15; + int32 newTargetGainL32Q15; + int32 drcRmsDBL32Q23, tempOutL32, outDrcRmsDBL32Q23, gainDiffL32Q15; + int16 drcRmsDBL16Q7, outDrcRmsDBL16Q7; + + int32 downSampleCounter = pDrcData->downSampleCounter; + /*DrcStateType*/uint32 currState = pDrcData->currState[0]; + int32 gainL32Q15 = pDrcData->gainL32Q15[0]; + uint32 timeConstantUL32Q31 = pDrcData->timeConstantUL32Q31[0]; + int32 dwcomp_state_change = pDrcData->dwcomp_state_change[0]; + int32 uwcomp_state_change = pDrcData->uwcomp_state_change[0]; + + for (i=0; idownSampleLevel; + + /* compute the current INPUT RMS in dB (log domain) */ + // log10_fixed: input L32Q0 format, output Q23 format + if (pDrcData->pRmsStateL32[i] != 0) { + drcRmsDBL32Q23 = log10_fixed(pDrcData->pRmsStateL32[i]); + } + else { + drcRmsDBL32Q23 = MIN_RMS_DB_L32Q23; + } + drcRmsDBL16Q7 = s16_extract_s32_h(drcRmsDBL32Q23); /* Q7 */ + drcRmsDBL16Q7 = s16_max_s16_s16(drcRmsDBL16Q7, MIN_RMS_DB_L16Q7); + + /* compute the current output RMS in dB (log domain) */ + // outDrcRmsDB = 10*log10(input*pDrcData->gainUL32Q15)^2 + // convert UL32 to L32 format that log10_fixed can take as input + tempOutL32 = s32_lsr_u32(gainL32Q15, 1); + // log10_fixed: input L32Q0 format, output Q23 format + if (tempOutL32 != 0) { + outDrcRmsDBL32Q23 = log10_fixed(tempOutL32); + outDrcRmsDBL32Q23 = s32_add_s32_s32( + (int32)s64_sub_s64_s64(s64_shl_s64(outDrcRmsDBL32Q23,1), + DB_16384_L32Q23), drcRmsDBL32Q23); + } + else { + outDrcRmsDBL32Q23 = MIN_RMS_DB_L32Q23; + } + outDrcRmsDBL16Q7 = s16_extract_s32_h(outDrcRmsDBL32Q23); + + /* Compute the Target Gain for the current sample based on input RMS */ + newTargetGainL32Q15 = ComputeTargetGain(pDrcCfg, drcRmsDBL16Q7); + + /* Find out the appropriate time constant based on output RMS */ + if (outDrcRmsDBL16Q7 > pDrcCfg->outDnCompThresholdL16Q7) { + if (newTargetGainL32Q15 < gainL32Q15) { + timeConstantUL32Q31 = pDrcCfg->dnCompAttackUL32Q31; + currState = ATTACK; + } + else if(newTargetGainL32Q15 >= s32_mult_s32_u16_shift15_sat(gainL32Q15, + pDrcCfg->dnCompHysterisisUL16Q14Asl1)) + { + timeConstantUL32Q31 = pDrcCfg->dnCompReleaseUL32Q31; + currState = RELEASE; + } + else { + if (currState == ATTACK) { + timeConstantUL32Q31 = 0; + currState = NO_CHANGE; + } + } + dwcomp_state_change = 0; /* CR480616 fix */ + } + else if (outDrcRmsDBL16Q7 < pDrcCfg->outDnExpaThresholdL16Q7) { + if (s32_mult_s32_u16_shift15_sat(newTargetGainL32Q15, + pDrcCfg->dnExpaHysterisisUL16Q14Asl1) < gainL32Q15) + { + timeConstantUL32Q31 = pDrcCfg->dnExpaAttackUL32Q31; + currState = ATTACK; + } + else if(newTargetGainL32Q15 > gainL32Q15) { + timeConstantUL32Q31 = pDrcCfg->dnExpaReleaseUL32Q31; + currState = RELEASE; + } + else { + if (currState == RELEASE) { + timeConstantUL32Q31 = 0; + currState = NO_CHANGE; + } + } + } + else if (outDrcRmsDBL16Q7 < pDrcCfg->outUpCompThresholdL16Q7) { + if (newTargetGainL32Q15 < gainL32Q15) { + timeConstantUL32Q31 = pDrcCfg->upCompAttackUL32Q31; + currState = ATTACK; + } + else if(newTargetGainL32Q15 >= s32_mult_s32_u16_shift15_sat(gainL32Q15, + pDrcCfg->upCompHysterisisUL16Q14Asl1)) + { + timeConstantUL32Q31 = pDrcCfg->upCompReleaseUL32Q31; + currState = RELEASE; + } + else { + if (currState == ATTACK) { + timeConstantUL32Q31 = 0; + currState = NO_CHANGE; + } + } + uwcomp_state_change = 0; /* CR480616 fix */ + } + else { + if (drcRmsDBL16Q7 > pDrcCfg->dnCompThresholdL16Q7) { + timeConstantUL32Q31 = pDrcCfg->dnCompAttackUL32Q31; + currState = ATTACK; + } + else if(drcRmsDBL16Q7 < pDrcCfg->dnExpaThresholdL16Q7) { + timeConstantUL32Q31 = pDrcCfg->dnExpaAttackUL32Q31; + currState = ATTACK; + } + else if (drcRmsDBL16Q7 < pDrcCfg->upCompThresholdL16Q7) { + timeConstantUL32Q31 = pDrcCfg->upCompReleaseUL32Q31; + currState = RELEASE; + } + else { + /* timeConstantUL32Q31 = 0; */ /* CR270058 fix */ + currState = NO_CHANGE; + /* CR480616 fix */ + if (dwcomp_state_change == 0) + { + timeConstantUL32Q31 = pDrcCfg->dnCompReleaseUL32Q31; + currState = RELEASE; + dwcomp_state_change = 1; + } + else if (uwcomp_state_change == 0) + { + timeConstantUL32Q31 = pDrcCfg->upCompAttackUL32Q31; + currState = ATTACK; + uwcomp_state_change = 1; + } + } + } + + /* calculate DRC gain with determined smooth factor */ + // drcGain = drcGain*(1-timeConstant) + drcTargetGain*timeConstant + // = drcGain + (drcTargetGain - drcGain)*timeConstant + gainDiffL32Q15 = s32_sub_s32_s32(newTargetGainL32Q15, gainL32Q15); + currDrcGainL40Q15 = s64_mult_s32_u32_shift(gainDiffL32Q15, timeConstantUL32Q31, 1); + currDrcGainL40Q15 = s64_add_s64_s64(currDrcGainL40Q15, gainL32Q15); + + gainL32Q15 = s32_saturate_s64(currDrcGainL40Q15); + } + + downSampleCounter--; + + pDrcData->paGainL32Q15[0][i] = gainL32Q15; + } + + pDrcData->downSampleCounter = downSampleCounter; + + /*For other than Hexagon processors we need below typecasting change otherwise we hit with compilation error */ +#ifdef QDSP6_DRCLIB_ASM + pDrcData->currState[0] = currState; +#else + pDrcData->currState[0] = (DrcStateType)currState; +#endif + + pDrcData->gainL32Q15[0] = gainL32Q15; + pDrcData->timeConstantUL32Q31[0] = timeConstantUL32Q31; + pDrcData->dwcomp_state_change[0] = dwcomp_state_change; + pDrcData->uwcomp_state_change[0] = uwcomp_state_change; +} + +#endif + + +#if ((defined __hexagon__) || (defined __qdsp6__)) + +extern "C" { +void CalculateDrcGainTwoGain(DrcConfigInternal *pDrcCfg, DrcData *pDrcData, uint32 nSampleCnt); +} + +#else + +static void CalculateDrcGainTwoGain(DrcConfigInternal *pDrcCfg, DrcData *pDrcData, uint32 nSampleCnt) +{ + uint32 i; + int64 currDrcGainL40Q15; + int32 newTargetGainL32Q15; + int32 drcRmsDBL32Q23, tempOutL32, outDrcRmsDBL32Q23, gainDiffL32Q15; + int16 drcRmsDBL16Q7, outDrcRmsDBL16Q7; + + int32 downSampleCounter = pDrcData->downSampleCounter; + + /*DrcStateType*/uint32 currStateL = pDrcData->currState[0]; + int32 gainL32Q15L = pDrcData->gainL32Q15[0]; + uint32 timeConstantUL32Q31L = pDrcData->timeConstantUL32Q31[0]; + int32 dwcomp_state_changeL = pDrcData->dwcomp_state_change[0]; + int32 uwcomp_state_changeL = pDrcData->uwcomp_state_change[0]; + + /*DrcStateType*/uint32 currStateR = pDrcData->currState[1]; + int32 gainL32Q15R = pDrcData->gainL32Q15[1]; + uint32 timeConstantUL32Q31R = pDrcData->timeConstantUL32Q31[1]; + int32 dwcomp_state_changeR = pDrcData->dwcomp_state_change[1]; + int32 uwcomp_state_changeR = pDrcData->uwcomp_state_change[1]; + + for (i=0; idownSampleLevel; + + /* compute the current INPUT RMS in dB (log domain) */ + // log10_fixed: input L32Q0 format, output Q23 format + if (pDrcData->pRmsStateL32[2*i] != 0) { + drcRmsDBL32Q23 = log10_fixed(pDrcData->pRmsStateL32[2*i]); + } + else { + drcRmsDBL32Q23 = MIN_RMS_DB_L32Q23; + } + drcRmsDBL16Q7 = s16_extract_s32_h(drcRmsDBL32Q23); /* Q7 */ + drcRmsDBL16Q7 = s16_max_s16_s16(drcRmsDBL16Q7, MIN_RMS_DB_L16Q7); + + /* compute the current output RMS in dB (log domain) */ + // outDrcRmsDB = 10*log10(input*pDrcData->gainUL32Q15)^2 + // convert UL32 to L32 format that log10_fixed can take as input + tempOutL32 = s32_lsr_u32(gainL32Q15L, 1); + // log10_fixed: input L32Q0 format, output Q23 format + if (tempOutL32 != 0) { + outDrcRmsDBL32Q23 = log10_fixed(tempOutL32); + outDrcRmsDBL32Q23 = s32_add_s32_s32( + (int32)s64_sub_s64_s64(s64_shl_s64(outDrcRmsDBL32Q23,1), + DB_16384_L32Q23), drcRmsDBL32Q23); + } + else { + outDrcRmsDBL32Q23 = MIN_RMS_DB_L32Q23; + } + outDrcRmsDBL16Q7 = s16_extract_s32_h(outDrcRmsDBL32Q23); + + /* Compute the Target Gain for the current sample based on input RMS */ + newTargetGainL32Q15 = ComputeTargetGain(pDrcCfg, drcRmsDBL16Q7); + + /* Find out the appropriate time constant based on output RMS */ + if (outDrcRmsDBL16Q7 > pDrcCfg->outDnCompThresholdL16Q7) { + if (newTargetGainL32Q15 < gainL32Q15L) { + timeConstantUL32Q31L = pDrcCfg->dnCompAttackUL32Q31; + currStateL = ATTACK; + } + else if(newTargetGainL32Q15 >= s32_mult_s32_u16_shift15_sat(gainL32Q15L, + pDrcCfg->dnCompHysterisisUL16Q14Asl1)) + { + timeConstantUL32Q31L = pDrcCfg->dnCompReleaseUL32Q31; + currStateL = RELEASE; + } + else { + if (currStateL == ATTACK) { + timeConstantUL32Q31L = 0; + currStateL = NO_CHANGE; + } + } + dwcomp_state_changeL = 0; /* CR480616 fix */ + } + else if (outDrcRmsDBL16Q7 < pDrcCfg->outDnExpaThresholdL16Q7) { + if (s32_mult_s32_u16_shift15_sat(newTargetGainL32Q15, + pDrcCfg->dnExpaHysterisisUL16Q14Asl1) < gainL32Q15L) + { + timeConstantUL32Q31L = pDrcCfg->dnExpaAttackUL32Q31; + currStateL = ATTACK; + } + else if(newTargetGainL32Q15 > gainL32Q15L) { + timeConstantUL32Q31L = pDrcCfg->dnExpaReleaseUL32Q31; + currStateL = RELEASE; + } + else { + if (currStateL == RELEASE) { + timeConstantUL32Q31L = 0; + currStateL = NO_CHANGE; + } + } + } + else if (outDrcRmsDBL16Q7 < pDrcCfg->outUpCompThresholdL16Q7) { + if (newTargetGainL32Q15 < gainL32Q15L) { + timeConstantUL32Q31L = pDrcCfg->upCompAttackUL32Q31; + currStateL = ATTACK; + } + else if(newTargetGainL32Q15 >= s32_mult_s32_u16_shift15_sat(gainL32Q15L, + pDrcCfg->upCompHysterisisUL16Q14Asl1)) + { + timeConstantUL32Q31L = pDrcCfg->upCompReleaseUL32Q31; + currStateL = RELEASE; + } + else { + if (currStateL == ATTACK) { + timeConstantUL32Q31L = 0; + currStateL = NO_CHANGE; + } + } + uwcomp_state_changeL = 0; /* CR480616 fix */ + } + else { + if (drcRmsDBL16Q7 > pDrcCfg->dnCompThresholdL16Q7) { + timeConstantUL32Q31L = pDrcCfg->dnCompAttackUL32Q31; + currStateL = ATTACK; + } + else if(drcRmsDBL16Q7 < pDrcCfg->dnExpaThresholdL16Q7) { + timeConstantUL32Q31L = pDrcCfg->dnExpaAttackUL32Q31; + currStateL = ATTACK; + } + else if (drcRmsDBL16Q7 < pDrcCfg->upCompThresholdL16Q7) { + timeConstantUL32Q31L = pDrcCfg->upCompReleaseUL32Q31; + currStateL = RELEASE; + } + else { + /* timeConstantUL32Q31L = 0; */ /* CR270058 fix */ + currStateL = NO_CHANGE; + /* CR480616 fix */ + if (dwcomp_state_changeL == 0) + { + timeConstantUL32Q31L = pDrcCfg->dnCompReleaseUL32Q31; + currStateL = RELEASE; + dwcomp_state_changeL = 1; + } else if (uwcomp_state_changeL == 0) { + timeConstantUL32Q31L = pDrcCfg->upCompAttackUL32Q31; + currStateL = ATTACK; + uwcomp_state_changeL = 1; + } + } + } + + /* calculate DRC gain with determined smooth factor */ + // drcGain = drcGain*(1-timeConstant) + drcTargetGain*timeConstant + // = drcGain + (drcTargetGain - drcGain)*timeConstant + gainDiffL32Q15 = s32_sub_s32_s32(newTargetGainL32Q15, gainL32Q15L); + currDrcGainL40Q15 = s64_mult_s32_u32_shift(gainDiffL32Q15, timeConstantUL32Q31L, 1); + currDrcGainL40Q15 = s64_add_s64_s64(currDrcGainL40Q15, gainL32Q15L); + + gainL32Q15L = s32_saturate_s64(currDrcGainL40Q15); + + + /* compute the current INPUT RMS in dB (log domain) */ + // log10_fixed: input L32Q0 format, output Q23 format + if (pDrcData->pRmsStateL32[2*i+1] != 0) { + drcRmsDBL32Q23 = log10_fixed(pDrcData->pRmsStateL32[2*i+1]); + } + else { + drcRmsDBL32Q23 = MIN_RMS_DB_L32Q23; + } + drcRmsDBL16Q7 = s16_extract_s32_h(drcRmsDBL32Q23); /* Q7 */ + drcRmsDBL16Q7 = s16_max_s16_s16(drcRmsDBL16Q7, MIN_RMS_DB_L16Q7); + + /* compute the current output RMS in dB (log domain) */ + // outDrcRmsDB = 10*log10(input*pDrcData->gainUL32Q15)^2 + // convert UL32 to L32 format that log10_fixed can take as input + tempOutL32 = s32_lsr_u32(gainL32Q15R, 1); + // log10_fixed: input L32Q0 format, output Q23 format + if (tempOutL32 != 0) { + outDrcRmsDBL32Q23 = log10_fixed(tempOutL32); + outDrcRmsDBL32Q23 = s32_add_s32_s32( + (int32)s64_sub_s64_s64(s64_shl_s64(outDrcRmsDBL32Q23,1), + DB_16384_L32Q23), drcRmsDBL32Q23); + } + else { + outDrcRmsDBL32Q23 = MIN_RMS_DB_L32Q23; + } + outDrcRmsDBL16Q7 = s16_extract_s32_h(outDrcRmsDBL32Q23); + + /* Compute the Target Gain for the current sample based on input RMS */ + newTargetGainL32Q15 = ComputeTargetGain(pDrcCfg, drcRmsDBL16Q7); + + /* Find out the appropriate time constant based on output RMS */ + if (outDrcRmsDBL16Q7 > pDrcCfg->outDnCompThresholdL16Q7) { + if (newTargetGainL32Q15 < gainL32Q15R) { + timeConstantUL32Q31R = pDrcCfg->dnCompAttackUL32Q31; + currStateR = ATTACK; + } + else if(newTargetGainL32Q15 >= s32_mult_s32_u16_shift15_sat(gainL32Q15R, + pDrcCfg->dnCompHysterisisUL16Q14Asl1)) + { + timeConstantUL32Q31R = pDrcCfg->dnCompReleaseUL32Q31; + currStateR = RELEASE; + } + else { + if (currStateR == ATTACK) { + timeConstantUL32Q31R = 0; + currStateR = NO_CHANGE; + } + } + dwcomp_state_changeR = 0; /* CR480616 fix */ + } + else if (outDrcRmsDBL16Q7 < pDrcCfg->outDnExpaThresholdL16Q7) { + if (s32_mult_s32_u16_shift15_sat(newTargetGainL32Q15, + pDrcCfg->dnExpaHysterisisUL16Q14Asl1) < gainL32Q15R) + { + timeConstantUL32Q31R = pDrcCfg->dnExpaAttackUL32Q31; + currStateR = ATTACK; + } + else if(newTargetGainL32Q15 > gainL32Q15R) { + timeConstantUL32Q31R = pDrcCfg->dnExpaReleaseUL32Q31; + currStateR = RELEASE; + } + else { + if (currStateR == RELEASE) { + timeConstantUL32Q31R = 0; + currStateR = NO_CHANGE; + } + } + } + else if (outDrcRmsDBL16Q7 < pDrcCfg->outUpCompThresholdL16Q7) { + if (newTargetGainL32Q15 < gainL32Q15R) { + timeConstantUL32Q31R = pDrcCfg->upCompAttackUL32Q31; + currStateR = ATTACK; + } + else if(newTargetGainL32Q15 >= s32_mult_s32_u16_shift15_sat(gainL32Q15R, + pDrcCfg->upCompHysterisisUL16Q14Asl1)) + { + timeConstantUL32Q31R = pDrcCfg->upCompReleaseUL32Q31; + currStateR = RELEASE; + } + else { + if (currStateR == ATTACK) { + timeConstantUL32Q31R = 0; + currStateR = NO_CHANGE; + } + } + uwcomp_state_changeR = 0; /* CR480616 fix */ + } + else { + if (drcRmsDBL16Q7 > pDrcCfg->dnCompThresholdL16Q7) { + timeConstantUL32Q31R = pDrcCfg->dnCompAttackUL32Q31; + currStateR = ATTACK; + } + else if(drcRmsDBL16Q7 < pDrcCfg->dnExpaThresholdL16Q7) { + timeConstantUL32Q31R = pDrcCfg->dnExpaAttackUL32Q31; + currStateR = ATTACK; + } + else if (drcRmsDBL16Q7 < pDrcCfg->upCompThresholdL16Q7) { + timeConstantUL32Q31R = pDrcCfg->upCompReleaseUL32Q31; + currStateR = RELEASE; + } + else { + /* timeConstantUL32Q31R = 0; */ /* CR270058 fix */ + currStateR = NO_CHANGE; + /* CR480616 fix */ + if (dwcomp_state_changeR == 0) { + timeConstantUL32Q31R = pDrcCfg->dnCompReleaseUL32Q31; + currStateR = RELEASE; + dwcomp_state_changeR = 1; + } else if (uwcomp_state_changeR == 0) { + timeConstantUL32Q31R = pDrcCfg->upCompAttackUL32Q31; + currStateR = ATTACK; + uwcomp_state_changeR = 1; + } + } + } + + /* calculate DRC gain with determined smooth factor */ + // drcGain = drcGain*(1-timeConstant) + drcTargetGain*timeConstant + // = drcGain + (drcTargetGain - drcGain)*timeConstant + gainDiffL32Q15 = s32_sub_s32_s32(newTargetGainL32Q15, gainL32Q15R); + currDrcGainL40Q15 = s64_mult_s32_u32_shift(gainDiffL32Q15, timeConstantUL32Q31R, 1); + currDrcGainL40Q15 = s64_add_s64_s64(currDrcGainL40Q15, gainL32Q15R); + + gainL32Q15R = s32_saturate_s64(currDrcGainL40Q15); + } + + downSampleCounter--; + + pDrcData->paGainL32Q15[0][i] = gainL32Q15L; + pDrcData->paGainL32Q15[1][i] = gainL32Q15R; + } + + pDrcData->downSampleCounter = downSampleCounter; + +#ifdef QDSP6_DRCLIB_ASM + pDrcData->currState[0] = currStateL; +#else + pDrcData->currState[0] = (DrcStateType)currStateL; +#endif + + pDrcData->gainL32Q15[0] = gainL32Q15L; + pDrcData->timeConstantUL32Q31[0] = timeConstantUL32Q31L; + pDrcData->dwcomp_state_change[0] = dwcomp_state_changeL; + pDrcData->uwcomp_state_change[0] = uwcomp_state_changeL; +#ifdef QDSP6_DRCLIB_ASM + pDrcData->currState[1] = currStateR; +#else + pDrcData->currState[1] = (DrcStateType)currStateR; +#endif + + pDrcData->gainL32Q15[1] = gainL32Q15R; + pDrcData->timeConstantUL32Q31[1] = timeConstantUL32Q31R; + pDrcData->dwcomp_state_change[1] = dwcomp_state_changeR; + pDrcData->uwcomp_state_change[1] = uwcomp_state_changeR; +} + +#endif + + +#if ((defined __hexagon__) || (defined __qdsp6__)) + +extern "C" { +void ApplyGainMono(DrcConfigInternal *pDrcCfg, DrcData *pDrcData, uint32 nSampleCnt); +} + +#else + +static void ApplyGainMono(DrcConfigInternal *pDrcCfg, DrcData *pDrcData, uint32 nSampleCnt) +{ + uint32 i; + int16 processData; + int32 tempOutL32; + int64 tempOutL64; + int16 *pOutputL16 = pDrcData->pDelayBufferLeftL16; + + if (MAKEUPGAIN_UNITY != pDrcCfg->makeupGainUL16Q12) { + for (i=0; ipDelayBufferLeftL16[i]; + tempOutL32 = s32_mult_s32_s16_shift15_sat(pDrcData->paGainL32Q15[0][i], processData); + + tempOutL64 = s64_mult_s32_u16_shift(tempOutL32, pDrcCfg->makeupGainUL16Q12, 4); + tempOutL32 = s32_saturate_s64(tempOutL64); + + *pOutputL16++ = s16_saturate_s32(tempOutL32); + } + } + else { + for (i=0; ipDelayBufferLeftL16[i]; + tempOutL32 = s32_mult_s32_s16_shift15_sat(pDrcData->paGainL32Q15[0][i], processData); + + *pOutputL16++ = s16_saturate_s32(tempOutL32); + } + } +} + +#endif + + +#if ((defined __hexagon__) || (defined __qdsp6__)) + +extern "C" { +void ApplyGainStereoLinked(DrcConfigInternal *pDrcCfg, DrcData *pDrcData, uint32 nSampleCnt); +} + +#else + +static void ApplyGainStereoLinked(DrcConfigInternal *pDrcCfg, DrcData *pDrcData, uint32 nSampleCnt) +{ + uint32 i; + int16 processData; + int32 tempOutL32; + int64 tempOutL64; + int16 *pOutputL16 = pDrcData->pDelayBufferLeftL16; + int16 *pOutputR16 = pDrcData->pDelayBufferRightL16; + + if (MAKEUPGAIN_UNITY != pDrcCfg->makeupGainUL16Q12) { + for (i=0; ipDelayBufferLeftL16[i]; + tempOutL32 = s32_mult_s32_s16_shift15_sat(pDrcData->paGainL32Q15[0][i], processData); + + tempOutL64 = s64_mult_s32_u16_shift(tempOutL32, pDrcCfg->makeupGainUL16Q12, 4); + tempOutL32 = s32_saturate_s64(tempOutL64); + + *pOutputL16++ = s16_saturate_s32(tempOutL32); + + processData = pDrcData->pDelayBufferRightL16[i]; + tempOutL32 = s32_mult_s32_s16_shift15_sat(pDrcData->paGainL32Q15[0][i], processData); + + tempOutL64 = s64_mult_s32_u16_shift(tempOutL32, pDrcCfg->makeupGainUL16Q12, 4); + tempOutL32 = s32_saturate_s64(tempOutL64); + + *pOutputR16++ = s16_saturate_s32(tempOutL32); + } + } + else { + for (i=0; ipDelayBufferLeftL16[i]; + tempOutL32 = s32_mult_s32_s16_shift15_sat(pDrcData->paGainL32Q15[0][i], processData); + + *pOutputL16++ = s16_saturate_s32(tempOutL32); + + processData = pDrcData->pDelayBufferRightL16[i]; + tempOutL32 = s32_mult_s32_s16_shift15_sat(pDrcData->paGainL32Q15[0][i], processData); + + *pOutputR16++ = s16_saturate_s32(tempOutL32); + } + } +} + +#endif + + +#if ((defined __hexagon__) || (defined __qdsp6__)) + +extern "C" { +void ApplyGainStereoUnLinked(DrcConfigInternal *pDrcCfg, DrcData *pDrcData, uint32 nSampleCnt); +} + +#else + +static void ApplyGainStereoUnLinked(DrcConfigInternal *pDrcCfg, DrcData *pDrcData, uint32 nSampleCnt) +{ + uint32 i; + int16 processData; + int32 tempOutL32; + int64 tempOutL64; + int16 *pOutputL16 = pDrcData->pDelayBufferLeftL16; + int16 *pOutputR16 = pDrcData->pDelayBufferRightL16; + + if (MAKEUPGAIN_UNITY != pDrcCfg->makeupGainUL16Q12) { + for (i=0; ipDelayBufferLeftL16[i]; + tempOutL32 = s32_mult_s32_s16_shift15_sat(pDrcData->paGainL32Q15[0][i], processData); + + tempOutL64 = s64_mult_s32_u16_shift(tempOutL32, pDrcCfg->makeupGainUL16Q12, 4); + tempOutL32 = s32_saturate_s64(tempOutL64); + + *pOutputL16++ = s16_saturate_s32(tempOutL32); + + processData = pDrcData->pDelayBufferRightL16[i]; + tempOutL32 = s32_mult_s32_s16_shift15_sat(pDrcData->paGainL32Q15[1][i], processData); + + tempOutL64 = s64_mult_s32_u16_shift(tempOutL32, pDrcCfg->makeupGainUL16Q12, 4); + tempOutL32 = s32_saturate_s64(tempOutL64); + + *pOutputR16++ = s16_saturate_s32(tempOutL32); + } + } + else { + for (i=0; ipDelayBufferLeftL16[i]; + tempOutL32 = s32_mult_s32_s16_shift15_sat(pDrcData->paGainL32Q15[0][i], processData); + + *pOutputL16++ = s16_saturate_s32(tempOutL32); + + processData = pDrcData->pDelayBufferRightL16[i]; + tempOutL32 = s32_mult_s32_s16_shift15_sat(pDrcData->paGainL32Q15[1][i], processData); + + *pOutputR16++ = s16_saturate_s32(tempOutL32); + } + } +} + +#endif + + +static void ProcessNoDrc(DrcConfigInternal *pDrcCfg, DrcData *pDrcData, + int16 *pOutPtrL16, int16 *pOutPtrR16, + int16 *pInPtrL16, int16 *pInPtrR16, + uint32 nSampleCnt) +{ + // copy data from input to output + if (pInPtrL16 != pOutPtrL16) { + memscpy(pOutPtrL16, sizeof(int16)*nSampleCnt, pInPtrL16, sizeof(int16)*nSampleCnt); + } + + if (2 == pDrcCfg->numChannel) { + if (pInPtrR16 != pOutPtrR16) { + memscpy(pOutPtrR16, sizeof(int16)*nSampleCnt, pInPtrR16, sizeof(int16)*nSampleCnt); + } + } +} + + +static void ProcessDrcOneChan(DrcConfigInternal *pDrcCfg, DrcData *pDrcData, + int16 *pOutPtrL16, int16 *pOutPtrR16, + int16 *pInPtrL16, int16 *pInPtrR16, + uint32 nSampleCnt) +{ + uint32 nSubFrameSize; + + while (nSampleCnt > 0) { + nSubFrameSize = s16_min_s16_s16(nSampleCnt, DRC_BLOCKSIZE); + nSampleCnt -= nSubFrameSize; + + memscpy(pDrcData->pDelayBufferLeftL16+pDrcCfg->delay, sizeof(int16)*nSubFrameSize, pInPtrL16, sizeof(int16)*nSubFrameSize); + + ComputeInputRmsMono(pDrcCfg, pDrcData, pInPtrL16, nSubFrameSize); + + CalculateDrcGainOneGain(pDrcCfg, pDrcData, nSubFrameSize); + + ApplyGainMono(pDrcCfg, pDrcData, nSubFrameSize); + + memscpy(pOutPtrL16, sizeof(int16)*nSubFrameSize, pDrcData->pDelayBufferLeftL16, sizeof(int16)*nSubFrameSize); + + memsmove(pDrcData->pDelayBufferLeftL16, sizeof(int16)*pDrcCfg->delay, pDrcData->pDelayBufferLeftL16+nSubFrameSize, sizeof(int16)*pDrcCfg->delay); + + pInPtrL16 += nSubFrameSize; + pOutPtrL16 += nSubFrameSize; + } +} + + +static void ProcessDrcTwoChanNotLinked(DrcConfigInternal *pDrcCfg, DrcData *pDrcData, + int16 *pOutPtrL16, int16 *pOutPtrR16, + int16 *pInPtrL16, int16 *pInPtrR16, + uint32 nSampleCnt) +{ + uint32 nSubFrameSize; + + while (nSampleCnt > 0) { + nSubFrameSize = s16_min_s16_s16(nSampleCnt, DRC_BLOCKSIZE); + nSampleCnt -= nSubFrameSize; + + memscpy(pDrcData->pDelayBufferLeftL16+pDrcCfg->delay, sizeof(int16)*nSubFrameSize, pInPtrL16, sizeof(int16)*nSubFrameSize); + memscpy(pDrcData->pDelayBufferRightL16+pDrcCfg->delay, sizeof(int16)*nSubFrameSize, pInPtrR16, sizeof(int16)*nSubFrameSize); + + ComputeInputRmsStereoUnLinked(pDrcCfg, pDrcData, pInPtrL16, pInPtrR16, nSubFrameSize); + + CalculateDrcGainTwoGain(pDrcCfg, pDrcData, nSubFrameSize); + + ApplyGainStereoUnLinked(pDrcCfg, pDrcData, nSubFrameSize); + + memscpy(pOutPtrL16, sizeof(int16)*nSubFrameSize, pDrcData->pDelayBufferLeftL16, sizeof(int16)*nSubFrameSize); + memscpy(pOutPtrR16, sizeof(int16)*nSubFrameSize, pDrcData->pDelayBufferRightL16, sizeof(int16)*nSubFrameSize); + + memsmove(pDrcData->pDelayBufferLeftL16, sizeof(int16)*pDrcCfg->delay, pDrcData->pDelayBufferLeftL16+nSubFrameSize, sizeof(int16)*pDrcCfg->delay); + memsmove(pDrcData->pDelayBufferRightL16, sizeof(int16)*pDrcCfg->delay, pDrcData->pDelayBufferRightL16+nSubFrameSize, sizeof(int16)*pDrcCfg->delay); + + pInPtrL16 += nSubFrameSize; + pInPtrR16 += nSubFrameSize; + + pOutPtrL16 += nSubFrameSize; + pOutPtrR16 += nSubFrameSize; + } +} + + +static void ProcessDrcTwoChanLinked(DrcConfigInternal *pDrcCfg, DrcData *pDrcData, + int16 *pOutPtrL16, int16 *pOutPtrR16, + int16 *pInPtrL16, int16 *pInPtrR16, + uint32 nSampleCnt) +{ + uint32 nSubFrameSize; + + while (nSampleCnt > 0) { + nSubFrameSize = s16_min_s16_s16(nSampleCnt, DRC_BLOCKSIZE); + nSampleCnt -= nSubFrameSize; + + memscpy(pDrcData->pDelayBufferLeftL16+pDrcCfg->delay, sizeof(int16)*nSubFrameSize, pInPtrL16, sizeof(int16)*nSubFrameSize); + memscpy(pDrcData->pDelayBufferRightL16+pDrcCfg->delay, sizeof(int16)*nSubFrameSize, pInPtrR16, sizeof(int16)*nSubFrameSize); + + ComputeInputRmsStereoLinked(pDrcCfg, pDrcData, pInPtrL16, pInPtrR16, nSubFrameSize); + + CalculateDrcGainOneGain(pDrcCfg, pDrcData, nSubFrameSize); + + ApplyGainStereoLinked(pDrcCfg, pDrcData, nSubFrameSize); + + memscpy(pOutPtrL16, sizeof(int16)*nSubFrameSize, pDrcData->pDelayBufferLeftL16, sizeof(int16)*nSubFrameSize); + memscpy(pOutPtrR16, sizeof(int16)*nSubFrameSize, pDrcData->pDelayBufferRightL16, sizeof(int16)*nSubFrameSize); + + memsmove(pDrcData->pDelayBufferLeftL16, sizeof(int16)*pDrcCfg->delay, pDrcData->pDelayBufferLeftL16+nSubFrameSize, sizeof(int16)*pDrcCfg->delay); + memsmove(pDrcData->pDelayBufferRightL16, sizeof(int16)*pDrcCfg->delay, pDrcData->pDelayBufferRightL16+nSubFrameSize, sizeof(int16)*pDrcCfg->delay); + + pInPtrL16 += nSubFrameSize; + pInPtrR16 += nSubFrameSize; + + pOutPtrL16 += nSubFrameSize; + pOutPtrR16 += nSubFrameSize; + } +} + + +/*---------------------------------------------------------------------------- + * Function Definitions + * -------------------------------------------------------------------------*/ +CDrcLib::CDrcLib() +{ + // Set default values for the tuning parameters + // Keep the initial integer parameters unmodified + m_drcCfgInt.numChannel = NUM_CHANNEL_DEFAULT; + m_drcCfgInt.stereoLinked = (DrcStereoLinkedType)STEREO_LINKED_DEFAULT; + + m_drcCfgInt.mode = (DrcFeaturesType)MODE_DEFAULT; + m_drcCfgInt.downSampleLevel = DOWNSAMPLE_LEVEL_DEFAULT; + m_drcCfgInt.delay = DELAY_DEFAULT; + m_drcCfgInt.rmsTavUL16Q16 = RMS_TAV_DEFAULT; + m_drcCfgInt.makeupGainUL16Q12 = MAKEUP_GAIN_DEFAULT; + + + m_drcCfgInt.dnExpaThresholdL16Q7 = DN_EXPA_THRESHOLD_DEFAULT; + m_drcCfgInt.dnExpaSlopeL16Q8 = DN_EXPA_SLOPE_DEFAULT; + m_drcCfgInt.dnExpaAttackUL32Q31 = DN_EXPA_ATTACK_DEFAULT; + m_drcCfgInt.dnExpaReleaseUL32Q31 = DN_EXPA_RELEASE_DEFAULT; + m_drcCfgInt.dnExpaHysterisisUL16Q14 = DN_EXPA_HYSTERISIS_DEFAULT; + m_drcCfgInt.dnExpaMinGainDBL32Q23 = DN_EXPA_MIN_GAIN_DEFAULT; + + m_drcCfgInt.upCompThresholdL16Q7 = UP_COMP_THRESHOLD_DEFAULT; + m_drcCfgInt.upCompSlopeUL16Q16 = UP_COMP_SLOPE_DEFAULT; + m_drcCfgInt.upCompAttackUL32Q31 = UP_COMP_ATTACK_DEFAULT; + m_drcCfgInt.upCompReleaseUL32Q31 = UP_COMP_RELEASE_DEFAULT; + m_drcCfgInt.upCompHysterisisUL16Q14 = UP_COMP_HYSTERISIS_DEFAULT; + + + m_drcCfgInt.dnCompThresholdL16Q7 = DN_COMP_THRESHOLD_DEFAULT; + m_drcCfgInt.dnCompSlopeUL16Q16 = DN_COMP_SLOPE_DEFAULT; + m_drcCfgInt.dnCompAttackUL32Q31 = DN_COMP_ATTACK_DEFAULT; + m_drcCfgInt.dnCompReleaseUL32Q31 = DN_COMP_RELEASE_DEFAULT; + m_drcCfgInt.dnCompHysterisisUL16Q14 = DN_COMP_HYSTERISIS_DEFAULT; + +} + + +/*====================================================================== + + FUNCTION Initialize + + DESCRIPTION Performs initialization of data structures for the + drc algorithm. Two pointers to two memory is passed + for configuring the drc static configuration + structure. + + Called once at audio connection set up time. + + PARAMETERS paramMiscValues: [in] Pointer to 16-bit tuning parameter list + paramStaticCurveValues: [in] Pointer to 32-bit tuning parameter list + m_drcCfg: [in,out] Pointer to configuration structure + m_drcCfg: [out] Pointer to data structure + + SIDE EFFECTS None + +======================================================================*/ +PPStatus CDrcLib::Initialize (DrcConfig &cfg) +{ + PPStatus errorCode = PPFAILURE; + + /* If delay configured is out of range, return error code */ + if (cfg.delay > MAX_DELAY_SAMPLE) + { + return (errorCode = PPERR_DELAY_INVALID); + } + else if (cfg.delay < 0) + { + return (errorCode = PPERR_DELAY_NEGATIVE); + } + else + { + // valid delay value + m_drcCfgInt.delay = cfg.delay; + } + + errorCode = ReInitialize(cfg); + if (PPSUCCESS != errorCode) + { + return errorCode; + } + + Reset(); + + ///////////////////////////////////////////////////////// + // End module initialization + ///////////////////////////////////////////////////////// + return PPSUCCESS; +} + + +PPStatus CDrcLib::ReInitialize (DrcConfig &cfg) +{ + int16 tempThresholdL16Q7; + + /*-------------- Initialize members of Misc part of the data structure --------------*/ + m_drcCfgInt.numChannel = cfg.numChannel; +#ifdef QDSP6_DRCLIB_ASM + m_drcCfgInt.stereoLinked = cfg.stereoLinked; +#else + m_drcCfgInt.stereoLinked = (DrcStereoLinkedType)cfg.stereoLinked; +#endif + + m_drcCfgInt.mode = (DrcFeaturesType) cfg.mode; + + m_drcCfgInt.downSampleLevel = cfg.downSampleLevel; + m_drcCfgInt.rmsTavUL16Q16 = cfg.rmsTavUL16Q16; + m_drcCfgInt.makeupGainUL16Q12 = cfg.makeupGainUL16Q12; + + // for unsigned Q16, (1-TAV) equals to -TAV + m_drcCfgInt.negativeDrcTavUL16Q16 = s16_neg_s16_sat(m_drcCfgInt.rmsTavUL16Q16); + + /*-------------- Initialize tuning parameters for Static Curve -----------------*/ + m_drcCfgInt.dnExpaThresholdL16Q7 = cfg.dnExpaThresholdL16Q7; + m_drcCfgInt.dnExpaSlopeL16Q8 = cfg.dnExpaSlopeL16Q8; + m_drcCfgInt.dnExpaAttackUL32Q31 = cfg.dnExpaAttackUL32Q31; + m_drcCfgInt.dnExpaReleaseUL32Q31 = cfg.dnExpaReleaseUL32Q31; + m_drcCfgInt.dnExpaHysterisisUL16Q14 = cfg.dnExpaHysterisisUL16Q14; + m_drcCfgInt.dnExpaMinGainDBL32Q23 = cfg.dnExpaMinGainDBL32Q23; + + m_drcCfgInt.dnExpaHysterisisUL16Q14Asl1 = m_drcCfgInt.dnExpaHysterisisUL16Q14<<1; + + m_drcCfgInt.upCompThresholdL16Q7 = cfg.upCompThresholdL16Q7; + m_drcCfgInt.upCompSlopeUL16Q16 = cfg.upCompSlopeUL16Q16; + m_drcCfgInt.upCompAttackUL32Q31 = cfg.upCompAttackUL32Q31; + m_drcCfgInt.upCompReleaseUL32Q31 = cfg.upCompReleaseUL32Q31; + m_drcCfgInt.upCompHysterisisUL16Q14 = cfg.upCompHysterisisUL16Q14; + + m_drcCfgInt.upCompHysterisisUL16Q14Asl1 = m_drcCfgInt.upCompHysterisisUL16Q14<<1; + + m_drcCfgInt.dnCompThresholdL16Q7 = cfg.dnCompThresholdL16Q7; + m_drcCfgInt.dnCompSlopeUL16Q16 = cfg.dnCompSlopeUL16Q16; + m_drcCfgInt.dnCompAttackUL32Q31 = cfg.dnCompAttackUL32Q31; + m_drcCfgInt.dnCompReleaseUL32Q31 = cfg.dnCompReleaseUL32Q31; + m_drcCfgInt.dnCompHysterisisUL16Q14 = cfg.dnCompHysterisisUL16Q14; + + m_drcCfgInt.dnCompHysterisisUL16Q14Asl1 = m_drcCfgInt.dnCompHysterisisUL16Q14<<1; + + m_drcCfgInt.outDnCompThresholdL16Q7 = m_drcCfgInt.dnCompThresholdL16Q7; + tempThresholdL16Q7 = s16_extract_s32_h(s32_mult_s16_u16(s16_sub_s16_s16(m_drcCfgInt.upCompThresholdL16Q7, + m_drcCfgInt.dnExpaThresholdL16Q7), + m_drcCfgInt.upCompSlopeUL16Q16)); /* Q23 */ + tempThresholdL16Q7 = s16_add_s16_s16_sat(tempThresholdL16Q7, m_drcCfgInt.dnExpaThresholdL16Q7); + m_drcCfgInt.outDnExpaThresholdL16Q7 = tempThresholdL16Q7; + m_drcCfgInt.outUpCompThresholdL16Q7 = m_drcCfgInt.upCompThresholdL16Q7; + + // (uwCompThresholdL16Q7 - dwExpaThresholdL16Q7)*uwCompSlopeUL16Q16 + m_drcCfgInt.dnExpaNewTargetGainL32Q23 = s32_mult_s16_u16(s16_sub_s16_s16(m_drcCfgInt.upCompThresholdL16Q7, + m_drcCfgInt.dnExpaThresholdL16Q7), m_drcCfgInt.upCompSlopeUL16Q16); + + // dwCompThreshold * dwCompSlopeUL16Q16 + m_drcCfgInt.dnCompThrMultSlopeL32Q23 = s32_mult_s16_u16(m_drcCfgInt.dnCompThresholdL16Q7, + m_drcCfgInt.dnCompSlopeUL16Q16); + + // uwCompThreshold * uwCompSlopeUL16Q16 + m_drcCfgInt.upCompThrMultSlopeL32Q23 = s32_mult_s16_u16(m_drcCfgInt.upCompThresholdL16Q7, + m_drcCfgInt.upCompSlopeUL16Q16); + + if (DRC_ENABLED == m_drcCfgInt.mode) { + if (1 == m_drcCfgInt.numChannel) { + fnpProcess = ProcessDrcOneChan; + } + else if (2 == m_drcCfgInt.numChannel) { + if (CHANNEL_NOT_LINKED == m_drcCfgInt.stereoLinked) { + fnpProcess = ProcessDrcTwoChanNotLinked; + } + else if (CHANNEL_LINKED == m_drcCfgInt.stereoLinked) { + fnpProcess = ProcessDrcTwoChanLinked; + } + else{ + fnpProcess = NULL; + return PPFAILURE; + } + } + else { + fnpProcess = NULL; + return PPFAILURE; + } + } + else if (DRC_DISABLED == m_drcCfgInt.mode) { + if (1==m_drcCfgInt.numChannel || 2==m_drcCfgInt.numChannel) { + fnpProcess = ProcessNoDrc; + } + else { + fnpProcess = NULL; + return PPFAILURE; + } + } + else { + fnpProcess = NULL; + return PPFAILURE; + } + + ///////////////////////////////////////////////////////// + // End module initialization + ///////////////////////////////////////////////////////// + return PPSUCCESS; +} + +void CDrcLib::Reset () +{ + /*-------------- Reset members varialbes --------------*/ + m_drcData.downSampleCounter = 0; // Reset the down sample counter + m_drcData.rmsStateL32[0] = 0; // Reset the RMS estimate value for Left channel + m_drcData.rmsStateL32[1] = 0; // Reset the RMS estimate value for Right channel + m_drcData.gainL32Q15[0] = 32768; + m_drcData.gainL32Q15[1] = 32768; + m_drcData.currState[0] = NO_CHANGE; + m_drcData.currState[1] = NO_CHANGE; + m_drcData.timeConstantUL32Q31[0] = 0; + m_drcData.timeConstantUL32Q31[1] = 0; + + m_drcData.paGainL32Q15[0] = m_aGainL32Q15[0]; + m_drcData.paGainL32Q15[1] = m_aGainL32Q15[1]; + + m_drcData.pRmsStateL32 = m_aRmsStateL32; + + m_drcData.pDelayBufferLeftL16 = m_delayBufferLeftL16; + m_drcData.pDelayBufferRightL16 = m_delayBufferRightL16; + m_drcData.dwcomp_state_change[0] = 1; + m_drcData.dwcomp_state_change[1] = 1; + m_drcData.uwcomp_state_change[0] = 1; + m_drcData.uwcomp_state_change[1] = 1; + + memset(m_drcData.pDelayBufferLeftL16, 0, sizeof(int16)*(m_drcCfgInt.delay+DRC_BLOCKSIZE));//sizeof(m_delayBufferLeftL16)); + memset(m_drcData.pDelayBufferRightL16,0, sizeof(int16)*(m_drcCfgInt.delay+DRC_BLOCKSIZE));//sizeof(m_delayBufferRightL16)); + + memset(m_drcData.pRmsStateL32, 0, sizeof(int32)*2*DRC_BLOCKSIZE);// Reset the RMS estimate value + + memset(m_drcData.paGainL32Q15[0], 0, sizeof(int32)*DRC_BLOCKSIZE); + memset(m_drcData.paGainL32Q15[1], 0, sizeof(int32)*DRC_BLOCKSIZE); +} + + +/*====================================================================== + + FUNCTION Process + + DESCRIPTION Process multi-channel input audio sample by sample and drcit the + input to specified threshold level. The input can be in any sampling + rate - 8, 16, 22.05, 32, 44.1, 48 KHz. The input is 16-bit Q15 and + the output is also in the form of 16-bit Q15. + + DEPENDENCIES Input pointers must not be NULL. + drc_init must be called prior to any call to drc_process. + + PARAMETERS pInPtrL16: [in] Pointer to 16-bit Q15 Left channel signal + pInPtrR16: [in] Pointer to 16-bit Q15 Right channel signal + pOutPtrL16: [out] Pointer to 16-bit Q15 Left channel output audio + pOutPtrR16: [out] Pointer to 16-bit Q15 Right channel output audio + + RETURN VALUE gainNum: [out] How many gain values need to be computed for each input sample. + + SIDE EFFECTS None. + +======================================================================*/ + +void CDrcLib::Process ( int16 *pOutPtrL16, + int16 *pOutPtrR16, + int16 *pInPtrL16, + int16 *pInPtrR16, + uint32 nSampleCnt) +{ + (*fnpProcess)(&m_drcCfgInt, &m_drcData, pOutPtrL16, pOutPtrR16, + pInPtrL16, pInPtrR16, nSampleCnt); +} diff --git a/modules/processing/gain_control/drc/lib/src/drc_lib.c b/modules/processing/gain_control/drc/lib/src/drc_lib.c new file mode 100644 index 0000000..31efc27 --- /dev/null +++ b/modules/processing/gain_control/drc/lib/src/drc_lib.c @@ -0,0 +1,2079 @@ +/*============================================================================ + FILE: CDrcLib.c + + OVERVIEW: Implements the drciter algorithm. + + DEPENDENCIES: None + + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause +============================================================================*/ + +/*---------------------------------------------------------------------------- + * Include Files + * -------------------------------------------------------------------------*/ + +#include "drc_lib.h" +#include +#include "audio_basic_op_ext.h" +#include "audio_log10.h" +#include "audio_exp10.h" + +/*---------------------------------------------------------------------------- + * Private Function Declarations + * -------------------------------------------------------------------------*/ +DRC_RESULT output_rms_comp(drc_lib_mem_t *drc_lib_mem_ptr); +DRC_RESULT drc_processing_defaults(drc_config_t *drc_config_ptr); +DRC_RESULT state_memory_defaults(drc_lib_t *drc_lib_ptr); + +DRC_RESULT drc_processing_mode(drc_static_struct_t * pStatic, + drc_feature_mode_t mode, + drc_channel_linking_t link, + drc_state_struct_t * state); +DRC_RESULT ProcessBP16(drc_state_struct_t * pState, + drc_static_struct_t *pStatic, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr); +DRC_RESULT ProcessBP32(drc_state_struct_t * pState, + drc_static_struct_t *pStatic, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr); +DRC_RESULT ProcessMono16(drc_lib_t *pDrcLib, + uint16 negativeDrcTavUL16Q16, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr); +DRC_RESULT ProcessMono32(drc_lib_t *pDrcLib, + uint16 negativeDrcTavUL16Q16, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr); +DRC_RESULT ProcessMC16Linked(drc_lib_t *pDrcLib, + uint16 negativeDrcTavUL16Q16, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr); +DRC_RESULT ProcessMC16Unlinked(drc_lib_t *pDrcLib, + uint16 negativeDrcTavUL16Q16, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr); +DRC_RESULT ProcessMC32Linked(drc_lib_t *pDrcLib, + uint16 negativeDrcTavUL16Q16, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr); +DRC_RESULT ProcessMC32Unlinked(drc_lib_t *pDrcLib, + uint16 negativeDrcTavUL16Q16, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr); + +#ifndef QDSP6_DRCLIB_ASM +static void compute_drc_gain(drc_state_struct_t *pState, drc_config_t *drc_config_ptr, uint32 gainNum); +#endif +/*---------------------------------------------------------------------------- + * Private Table Definitions + * -------------------------------------------------------------------------*/ +#ifdef PROD_SPECIFIC_MAX_CH +int16 rms_constant_factor[MAX_NUM_CHANNEL + 1] = + { 0, 32767, 16384, 10923, 8192, 6554, 5461, 4681, 4096, 3641, 3277, 2979, 2731, 2521, 2341, 2185, 2048, + 1928, 1820, 1725, 1638, 1560, 1489, 1425, 1365, 1311, 1260, 1214, 1170, 1130, 1092, 1057, 1024, 993, + 964, 936, 910, 886, 862, 840, 819, 799, 780, 762, 745, 728, 712, 697, 683, 669, 655, + 643, 630, 618, 607, 596, 585, 575, 565, 555, 546, 537, 529, 520, 512, 504, 496, 489, + 482, 475, 468, 462, 455, 449, 443, 437, 431, 426, 420, 415, 410, 405, 400, 395, 390, + 386, 381, 377, 372, 368, 364, 360, 356, 352, 349, 345, 341, 338, 334, 331, 328, 324, + 321, 318, 315, 312, 309, 306, 303, 301, 298, 295, 293, 290, 287, 285, 282, 280, 278, + 275, 273, 271, 269, 266, 264, 262, 260, 258, 256 }; +#else +int16 rms_constant_factor[MAX_NUM_CHANNEL + 1] = + { 0, 32767, 16384, 10923, 8192, 6554, 5461, 4681, 4096, 3641, 3277, 2979, 2731, 2521, 2341, 2185, 2048, + 1928, 1820, 1725, 1638, 1560, 1489, 1425, 1365, 1311, 1260, 1214, 1170, 1130, 1092, 1057, 1024 }; +#endif + +/*---------------------------------------------------------------------------- + * Function Definitions + * -------------------------------------------------------------------------*/ +/*====================================================================== + +FUNCTION drc_get_mem_req + +DESCRIPTION Determine lib mem size. Called once at audio connection set up time. + +DEPENDENCIES Input pointers must not be NULL. + +PARAMETERS drc_lib_mem_requirements_ptr: [out] Pointer to lib mem requirements structure +drc_static_struct_ptr: [in] Pointer to static structure + +SIDE EFFECTS None + +======================================================================*/ + +DRC_RESULT drc_get_mem_req(drc_lib_mem_requirements_t *drc_lib_mem_requirements_ptr, + drc_static_struct_t * drc_static_struct_ptr) + +{ + uint32 libMemStructSize; + uint32 staticStructSize; + uint32 featureModeStructSize; + uint32 processingStructSize; + + uint32 stateStructSize, stateSize; + uint32 delayBufferSize; + uint32 delayBufferSizePerChannel; + uint32 size; + + // check if num_channel is valid for the c-sim + if (drc_static_struct_ptr->num_channel > MAX_NUM_CHANNEL) + { + return DRC_FAILURE; + } + + // clear memory + memset(drc_lib_mem_requirements_ptr, 0, sizeof(drc_lib_mem_requirements_t)); + + // determine mem size + libMemStructSize = sizeof(drc_lib_mem_t); + libMemStructSize = ALIGN8(libMemStructSize); + staticStructSize = sizeof(drc_static_struct_t); + staticStructSize = ALIGN8(staticStructSize); + featureModeStructSize = sizeof(drc_feature_mode_t); + featureModeStructSize = ALIGN8(featureModeStructSize); + processingStructSize = sizeof(drc_config_t); + processingStructSize = ALIGN8(processingStructSize); + + stateStructSize = sizeof(drc_state_struct_t); + stateStructSize = ALIGN8(stateStructSize); + + size = sizeof(int8 *) * drc_static_struct_ptr->num_channel; + size = ALIGN8(size); + size += sizeof(int32) * drc_static_struct_ptr->num_channel; + size = ALIGN8(size); + size += sizeof(int32) * drc_static_struct_ptr->num_channel; + size = ALIGN8(size); + size += sizeof(uint32) * drc_static_struct_ptr->num_channel; + size = ALIGN8(size); + size += sizeof(uint32) * drc_static_struct_ptr->num_channel; + size = ALIGN8(size); + size += sizeof(DrcStateType) * drc_static_struct_ptr->num_channel; + size = ALIGN8(size); + size += sizeof(uint32) * drc_static_struct_ptr->num_channel; + size = ALIGN8(size); + size += sizeof(int32) * drc_static_struct_ptr->num_channel; + size = ALIGN8(size); + size += sizeof(int32) * drc_static_struct_ptr->num_channel; + size = ALIGN8(size); + size += sizeof(int32) * drc_static_struct_ptr->num_channel; + size = ALIGN8(size); + size += sizeof(uint64) * drc_static_struct_ptr->num_channel; + stateSize = ALIGN8(size); + + delayBufferSizePerChannel = (uint32)(BITS_16 == drc_static_struct_ptr->data_width + ? s64_shl_s64(s64_add_s32_u32(ONE, drc_static_struct_ptr->delay), ONE) + : s64_shl_s64(s64_add_s32_u32(ONE, drc_static_struct_ptr->delay), TWO)); + delayBufferSizePerChannel = (uint32)(ALIGN8(delayBufferSizePerChannel)); + delayBufferSize = (uint32)(drc_static_struct_ptr->num_channel * delayBufferSizePerChannel); + + // lib memory arrangement + + // ------------------- ----> drc_lib_mem_requirements_ptr->lib_mem_size + // drc_lib_mem_t + // ------------------- + // drc_static_struct_t + // ------------------- + // drc_feature_mode_t + // ------------------- + // drc_processing_t + // ------------------- + // drc_state_struct_t + // ------------------- + // states + // ------------------- + // delay buffer + // ------------------- + + // total lib mem needed = drc_lib_mem_t + drc_static_struct_t + drc_feature_mode_t + drc_processing_t + + // drc_state_struct_t + stateSize + delay buffer + drc_lib_mem_requirements_ptr->lib_mem_size = libMemStructSize + staticStructSize + featureModeStructSize + + processingStructSize + stateStructSize + stateSize + delayBufferSize; + + // maximal lib stack mem consumption + drc_lib_mem_requirements_ptr->lib_stack_size = drc_max_stack_size; + + return DRC_SUCCESS; +} + +/*====================================================================== + +FUNCTION drc_init_memory + +DESCRIPTION Performs partition(allocation) and initialization of lib memory for the +drc algorithm. Called once at audio connection set up time. + +DEPENDENCIES Input pointers must not be NULL. + +PARAMETERS drc_lib_ptr: [in, out] Pointer to lib structure +drc_static_struct_ptr: [in] Pointer to static structure +pMem: [in] Pointer to the lib memory +memSize: [in] Size of the memory pointed by pMem + +SIDE EFFECTS None + +======================================================================*/ +DRC_RESULT drc_init_memory(drc_lib_t * drc_lib_ptr, + drc_static_struct_t *drc_static_struct_ptr, + int8 * pMem, + uint32 memSize) +{ + drc_lib_mem_t *pDrcLibMem = NULL; + int8 * pTemp = pMem; + uint32 channelNo, delayBufferSizePerChannel; + + uint32 libMemSize, libMemStructSize, staticStructSize, featureModeStructSize, processingStructSize, stateStructSize, + stateSize, delayBufferSize; + uint32 stateSize1, stateSize2, stateSize3, stateSize4, stateSize5, stateSize6, stateSize7, stateSize8, stateSize9, + stateSize10, stateSize11; + + // check if num_channel is valid for the c-sim + if (drc_static_struct_ptr->num_channel > MAX_NUM_CHANNEL) + { + return DRC_FAILURE; + } + + // re-calculate lib mem size + libMemStructSize = ALIGN8(sizeof(drc_lib_mem_t)); + staticStructSize = ALIGN8(sizeof(drc_static_struct_t)); + featureModeStructSize = ALIGN8(sizeof(drc_feature_mode_t)); + processingStructSize = ALIGN8(sizeof(drc_config_t)); + + stateStructSize = ALIGN8(sizeof(drc_state_struct_t)); + stateSize1 = ALIGN8(sizeof(int8 *) * drc_static_struct_ptr->num_channel); + stateSize2 = ALIGN8(sizeof(int32) * drc_static_struct_ptr->num_channel); + stateSize3 = ALIGN8(sizeof(int32) * drc_static_struct_ptr->num_channel); + stateSize4 = ALIGN8(sizeof(uint32) * drc_static_struct_ptr->num_channel); + stateSize5 = ALIGN8(sizeof(uint32) * drc_static_struct_ptr->num_channel); + stateSize6 = ALIGN8(sizeof(DrcStateType) * drc_static_struct_ptr->num_channel); + stateSize7 = ALIGN8(sizeof(uint32) * drc_static_struct_ptr->num_channel); + stateSize8 = ALIGN8(sizeof(int32) * drc_static_struct_ptr->num_channel); + stateSize9 = ALIGN8(sizeof(int32) * drc_static_struct_ptr->num_channel); + stateSize10 = ALIGN8(sizeof(int32) * drc_static_struct_ptr->num_channel); + stateSize11 = ALIGN8(sizeof(uint64) * drc_static_struct_ptr->num_channel); + stateSize = stateSize1 + stateSize2 + stateSize3 + stateSize4 + stateSize5 + stateSize6 + stateSize7 + stateSize8 + + stateSize9 + stateSize10 + stateSize11; + + delayBufferSizePerChannel = + (uint32)(ALIGN8(BITS_16 == drc_static_struct_ptr->data_width + ? s64_shl_s64(s64_add_s32_u32(ONE, drc_static_struct_ptr->delay), ONE) + : s64_shl_s64(s64_add_s32_u32(ONE, drc_static_struct_ptr->delay), TWO))); + delayBufferSize = (uint32)(drc_static_struct_ptr->num_channel * delayBufferSizePerChannel); + + // total lib mem needed = drc_lib_mem_t + drc_static_struct_t + drc_feature_mode_t + drc_processing_t + + // drc_state_struct_t + stateSize + delay buffer + libMemSize = libMemStructSize + staticStructSize + featureModeStructSize + processingStructSize + stateStructSize + + stateSize + delayBufferSize; + + // error out if the mem space given is not enough + if (memSize < libMemSize) + { + return DRC_MEMERROR; + } + + // before initializing lib_mem_ptr, it is FW job to make sure that pMem is 8 bytes aligned(with enough space) + memset(pMem, 0, memSize); // clear the mem + drc_lib_ptr->lib_mem_ptr = pMem; // init drc_lib_t; + + // lib memory arrangement + // ------------------- ----> drc_lib_ptr->lib_mem_ptr + // drc_lib_mem_t + // ------------------- + // drc_static_struct_t + // ------------------- + // drc_feature_mode_t + // ------------------- + // drc_processing_t + // ------------------- + // drc_state_struct_t + // ------------------- + // states + // ------------------- + // delay buffer + // ------------------- + + // lib memory partition starts here + pDrcLibMem = (drc_lib_mem_t *)drc_lib_ptr->lib_mem_ptr; // allocate memory for drc_lib_mem_t + pTemp += libMemStructSize; // pTemp points to where drc_static_struct_t will be located + + pDrcLibMem->drc_static_struct_ptr = + (drc_static_struct_t *)pTemp; // init drc_lib_mem_t; allocate memory for drc_static_struct_t + pDrcLibMem->drc_static_struct_size = staticStructSize; // init drc_lib_mem_t + pTemp += pDrcLibMem->drc_static_struct_size; // pTemp points to where drc_feature_mode_t will be located + + // init drc_static_struct_t + pDrcLibMem->drc_static_struct_ptr->data_width = drc_static_struct_ptr->data_width; + pDrcLibMem->drc_static_struct_ptr->sample_rate = drc_static_struct_ptr->sample_rate; + pDrcLibMem->drc_static_struct_ptr->num_channel = drc_static_struct_ptr->num_channel; + pDrcLibMem->drc_static_struct_ptr->delay = drc_static_struct_ptr->delay; + + pDrcLibMem->drc_feature_mode_ptr = + (drc_feature_mode_t *)pTemp; // init drc_lib_mem_t; allocate memory for drc_feature_mode_t + pDrcLibMem->drc_feature_mode_size = featureModeStructSize; // init drc_lib_mem_t + pTemp += pDrcLibMem->drc_feature_mode_size; // pTemp points to where drc_processing_t will be located + + // init drc_processing_t with defaults + *pDrcLibMem->drc_feature_mode_ptr = (drc_feature_mode_t)MODE_DEFAULT; + + pDrcLibMem->drc_config_ptr = (drc_config_t *)pTemp; // init drc_lib_mem_t; allocate memory for drc_processing_t + pDrcLibMem->drc_config_size = processingStructSize; // init drc_lib_mem_t + pTemp += pDrcLibMem->drc_config_size; // pTemp points to where drc_state_struct_t will be located + + // init drc_processing_t with defaults + if (drc_processing_defaults(pDrcLibMem->drc_config_ptr) != DRC_SUCCESS) + { + return DRC_FAILURE; + } + + pDrcLibMem->drc_state_struct_ptr = + (drc_state_struct_t *)pTemp; // init drc_lib_mem_t; allocate memory for drc_state_struct_t + pDrcLibMem->drc_state_struct_size = stateStructSize; // init drc_lib_mem_t + pTemp += pDrcLibMem->drc_state_struct_size; // pTemp points to where delayBuffer will be pointing to + + // init drc_state_struct_t + pDrcLibMem->drc_state_struct_ptr->delayBuffer = (int8 **)pTemp; + pTemp += stateSize1; // pTemp points to where rmsStateL32 will be pointing to + + pDrcLibMem->drc_state_struct_ptr->rmsStateL32 = (int32 *)pTemp; + pTemp += stateSize2; // pTemp points to where drcRmsDBL32Q23 will be pointing to + + pDrcLibMem->drc_state_struct_ptr->drcRmsDBL32Q23 = (int32 *)pTemp; + pTemp += stateSize3; // pTemp points to where targetGainUL32Q15 will be pointing to + + pDrcLibMem->drc_state_struct_ptr->targetGainUL32Q15 = (uint32 *)pTemp; + pTemp += stateSize4; // pTemp points to where gainUL32Q15 will be pointing to + + pDrcLibMem->drc_state_struct_ptr->gainUL32Q15 = (uint32 *)pTemp; + pTemp += stateSize5; // pTemp points to where currState will be pointing to + + pDrcLibMem->drc_state_struct_ptr->currState = (DrcStateType *)pTemp; + pTemp += stateSize6; // pTemp points to where timeConstantUL32Q31 will be pointing to + + pDrcLibMem->drc_state_struct_ptr->timeConstantUL32Q31 = (uint32 *)pTemp; + pTemp += stateSize7; // pTemp points to where delayBuffer[0] will be pointing to + + pDrcLibMem->drc_state_struct_ptr->dwcomp_state_change = (int32 *)pTemp; + pTemp += stateSize8; // pTemp points to where dwcomp_state_change[0] will be pointing to + + pDrcLibMem->drc_state_struct_ptr->uwcomp_state_change = (int32 *)pTemp; + pTemp += stateSize9; // pTemp points to where uwcomp_state_change[0] will be pointing to + pDrcLibMem->drc_state_struct_ptr->dnexpa_state_change = (int32 *)pTemp; + pTemp += stateSize10; // pTemp points to where instGainUL64Q27[0] will be pointing to + + pDrcLibMem->drc_state_struct_ptr->instGainUL64Q27 = (uint64 *)pTemp; + pTemp += stateSize11; + + // initialize dwcomp_state_change and uwcomp_state_change to 1 for each channel + for (channelNo = 0; channelNo < drc_static_struct_ptr->num_channel; channelNo++) + { + pDrcLibMem->drc_state_struct_ptr->dwcomp_state_change[channelNo] = 1; + pDrcLibMem->drc_state_struct_ptr->uwcomp_state_change[channelNo] = 1; + pDrcLibMem->drc_state_struct_ptr->dnexpa_state_change[channelNo] = 1; + } + + pDrcLibMem->drc_state_struct_ptr->inputIndex = drc_static_struct_ptr->delay; + pDrcLibMem->drc_state_struct_ptr->processIndex = 0; // Initialize the current index of the delay buffer + pDrcLibMem->drc_state_struct_ptr->downSampleCounter = 0; // Reset the down sample counter + + // Note: This function needs to be called to set up new output RMS thresholds every time the input RMS thresholds + // and/or slope change + if (output_rms_comp(pDrcLibMem) != DRC_SUCCESS) + { + return DRC_FAILURE; + } + + // init the states section + // init delayBuffer[channelNo] in the states section; allocate memory for delay buffer + for (channelNo = 0; channelNo < drc_static_struct_ptr->num_channel; channelNo++) + { + pDrcLibMem->drc_state_struct_ptr->delayBuffer[channelNo] = pTemp; + pTemp += delayBufferSizePerChannel; + } + // init the rest of the states + if (state_memory_defaults(drc_lib_ptr) != DRC_SUCCESS) + { + return DRC_FAILURE; + } + + // check to see if memory partition is correct + if (pTemp != (int8 *)pMem + libMemSize) + { + return DRC_MEMERROR; + } + + // update drc processing mode + drc_processing_mode(pDrcLibMem->drc_static_struct_ptr, + *pDrcLibMem->drc_feature_mode_ptr, + (drc_channel_linking_t)pDrcLibMem->drc_config_ptr->channelLinked, + pDrcLibMem->drc_state_struct_ptr); + + return DRC_SUCCESS; +} + +/*====================================================================== + +FUNCTION drc_processing_defaults + +DESCRIPTION Performs initialization of DRC calibration structure with default values + +DEPENDENCIES Input pointers must not be NULL. + +PARAMETERS pProcessing: [in, out] Pointer to DRC processing structure + +SIDE EFFECTS None + +======================================================================*/ +DRC_RESULT drc_processing_defaults(drc_config_t *drc_config_ptr) +{ + drc_config_ptr->channelLinked = CHANNEL_LINKED_DEFAULT; + drc_config_ptr->downSampleLevel = DOWNSAMPLE_LEVEL_DEFAULT; + drc_config_ptr->rmsTavUL16Q16 = RMS_TAV_DEFAULT; + drc_config_ptr->makeupGainUL16Q12 = MAKEUP_GAIN_DEFAULT; + + drc_config_ptr->dnExpaThresholdL16Q7 = DN_EXPA_THRESHOLD_DEFAULT; + drc_config_ptr->dnExpaSlopeL16Q8 = DN_EXPA_SLOPE_DEFAULT; + drc_config_ptr->dnExpaHysterisisUL16Q14 = DN_EXPA_HYSTERISIS_DEFAULT; + drc_config_ptr->dnExpaAttackUL32Q31 = DN_EXPA_ATTACK_DEFAULT; + drc_config_ptr->dnExpaReleaseUL32Q31 = DN_EXPA_RELEASE_DEFAULT; + drc_config_ptr->dnExpaMinGainDBL32Q23 = DN_EXPA_MIN_GAIN_DEFAULT; + + drc_config_ptr->upCompThresholdL16Q7 = UP_COMP_THRESHOLD_DEFAULT; + drc_config_ptr->upCompSlopeUL16Q16 = UP_COMP_SLOPE_DEFAULT; + drc_config_ptr->upCompAttackUL32Q31 = UP_COMP_ATTACK_DEFAULT; + drc_config_ptr->upCompReleaseUL32Q31 = UP_COMP_RELEASE_DEFAULT; + drc_config_ptr->upCompHysterisisUL16Q14 = UP_COMP_HYSTERISIS_DEFAULT; + + drc_config_ptr->dnCompThresholdL16Q7 = DN_COMP_THRESHOLD_DEFAULT; + drc_config_ptr->dnCompSlopeUL16Q16 = DN_COMP_SLOPE_DEFAULT; + drc_config_ptr->dnCompHysterisisUL16Q14 = DN_COMP_HYSTERISIS_DEFAULT; + drc_config_ptr->dnCompAttackUL32Q31 = DN_COMP_ATTACK_DEFAULT; + drc_config_ptr->dnCompReleaseUL32Q31 = DN_COMP_RELEASE_DEFAULT; + + return DRC_SUCCESS; +} + +/*====================================================================== + +FUNCTION state_memory_defaults + +DESCRIPTION Performs initialization of DRC state structure with default values + +DEPENDENCIES Input pointers must not be NULL. + +PARAMETERS pDrcLib: [in, out] Pointer to DRC lib structure + +SIDE EFFECTS None + +======================================================================*/ + +DRC_RESULT state_memory_defaults(drc_lib_t *pDrcLib) + +{ + drc_lib_mem_t * pDrcLibMem = (drc_lib_mem_t *)pDrcLib->lib_mem_ptr; + drc_static_struct_t *pStatic = pDrcLibMem->drc_static_struct_ptr; + drc_state_struct_t * pState = pDrcLibMem->drc_state_struct_ptr; + uint32 channelNo; + + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + { + pState->rmsStateL32[channelNo] = 0; + pState->drcRmsDBL32Q23[channelNo] = MIN_RMS_DB_L32Q23; // rmsStateL32 in log domain + pState->targetGainUL32Q15[channelNo] = 32768; // target gain + pState->gainUL32Q15[channelNo] = 32768; // smoothed gain + pState->currState[channelNo] = NO_CHANGE; + pState->timeConstantUL32Q31[channelNo] = 0; + pState->instGainUL64Q27[channelNo] = 134217728; // 2^27 + } + + return DRC_SUCCESS; +} + +/*====================================================================== + +FUNCTION output_rms_comp + +DESCRIPTION Performs calculation of output RMS thresholds based on +input RMS thresholds and the static curve slopes + +DEPENDENCIES Input pointers must not be NULL. + +PARAMETERS drc_lib_mem_ptr: [in, out] Pointer to DRC lib mem structure + +SIDE EFFECTS None + +======================================================================*/ +DRC_RESULT output_rms_comp(drc_lib_mem_t *drc_lib_mem_ptr) +{ + int16 tempThresholdL16Q7; + + tempThresholdL16Q7 = + s16_extract_s32_h(s32_mult_s16_u16(s16_sub_s16_s16(drc_lib_mem_ptr->drc_config_ptr->upCompThresholdL16Q7, + drc_lib_mem_ptr->drc_config_ptr->dnExpaThresholdL16Q7), + drc_lib_mem_ptr->drc_config_ptr->upCompSlopeUL16Q16)); // Q23 + drc_lib_mem_ptr->drc_state_struct_ptr->outDnExpaThresholdL16Q7 = + s16_add_s16_s16_sat(tempThresholdL16Q7, drc_lib_mem_ptr->drc_config_ptr->dnExpaThresholdL16Q7); + drc_lib_mem_ptr->drc_state_struct_ptr->outDnCompThresholdL16Q7 = + drc_lib_mem_ptr->drc_config_ptr->dnCompThresholdL16Q7; + drc_lib_mem_ptr->drc_state_struct_ptr->outUpCompThresholdL16Q7 = + drc_lib_mem_ptr->drc_config_ptr->upCompThresholdL16Q7; + + return DRC_SUCCESS; +} + +/*====================================================================== + +FUNCTION drc_get_param + +DESCRIPTION Get the default calibration params from pDRCLib and store in pMem + +DEPENDENCIES Input pointers must not be NULL. + +PARAMETERS pDrcLib: [in] Pointer to lib structure +paramID: [in] ID of the param +pMem: [out] Pointer to the memory where params are to be stored +memSize:[in] Size of the memory pointed by pMem +pParamSize: [out] Pointer to param size which indicates the size of the retrieved param(s) + +SIDE EFFECTS None + +======================================================================*/ +DRC_RESULT drc_get_param(drc_lib_t *pDrcLib, uint32 paramID, int8 *pMem, uint32 memSize, uint32 *pParamSize) +{ + drc_lib_mem_t *pDrcLibMem = (drc_lib_mem_t *)pDrcLib->lib_mem_ptr; + if (NULL == pDrcLibMem) + { + return DRC_MEMERROR; + } + + drc_static_struct_t *pStatic = pDrcLibMem->drc_static_struct_ptr; + + memset(pMem, 0, memSize); + + switch (paramID) + { + case DRC_PARAM_FEATURE_MODE: + { + // check if the memory buffer has enough space to write the parameter data + if (memSize >= sizeof(drc_feature_mode_t)) + { + + drc_feature_mode_t *drc_feature_mode_ptr = (drc_feature_mode_t *)pMem; + *drc_feature_mode_ptr = *pDrcLibMem->drc_feature_mode_ptr; + + *pParamSize = sizeof(drc_feature_mode_t); + } + else + { + return DRC_MEMERROR; + } + break; + } + case DRC_PARAM_CONFIG: + { + // check if the memory buffer has enough space to write the parameter data + if (memSize >= sizeof(drc_config_t)) + { + + *(drc_config_t *)pMem = *pDrcLibMem->drc_config_ptr; + + *pParamSize = sizeof(drc_config_t); + } + else + { + return DRC_MEMERROR; + } + break; + } + case DRC_PARAM_GET_LIB_VER: + { + // check if the memory buffer has enough space to write the parameter data + if (memSize >= sizeof(drc_lib_ver_t)) + { + *(drc_lib_ver_t *)pMem = DRC_LIB_VER; + *pParamSize = sizeof(drc_lib_ver_t); + } + else + { + return DRC_MEMERROR; + } + break; + } + case DRC_PARAM_GET_DELAY: + { + // check if the memory buffer has enough space to write the parameter data + if (memSize >= sizeof(drc_delay_t)) + { + *(drc_delay_t *)pMem = (drc_delay_t)pStatic->delay; + *pParamSize = sizeof(drc_delay_t); + } + else + { + return DRC_MEMERROR; + } + break; + } + default: + { + + return DRC_FAILURE; + } + } + + return DRC_SUCCESS; +} + +/*====================================================================== + +FUNCTION drc_set_param + +DESCRIPTION Set the calibration params in the lib memory using the values pointed by pMem + +DEPENDENCIES Input pointers must not be NULL. + +PARAMETERS pDrcLib: [in, out] Pointer to lib structure +paramID: [in] ID of the param +pMem: [in] Pointer to the memory where the values stored are used to set up the params in the lib memory +memSize:[in] Size of the memory pointed by pMem + +SIDE EFFECTS None + +======================================================================*/ +DRC_RESULT drc_set_param(drc_lib_t *pDrcLib, uint32 paramID, int8 *pMem, uint32 memSize) +{ + drc_lib_mem_t *pDrcLibMem = (drc_lib_mem_t *)pDrcLib->lib_mem_ptr; + if (NULL == pDrcLibMem) + { + return DRC_MEMERROR; + } + + drc_static_struct_t *pStatic = pDrcLibMem->drc_static_struct_ptr; + drc_state_struct_t * pState = pDrcLibMem->drc_state_struct_ptr; + uint32 channelNo; + + switch (paramID) + { + case DRC_PARAM_FEATURE_MODE: + { + // copy only when mem size matches to what is allocated in the lib memory + if (memSize == sizeof(drc_feature_mode_t)) + { + // set the calibration params in the lib memory + *pDrcLibMem->drc_feature_mode_ptr = *(drc_feature_mode_t *)pMem; + + // update drc processing mode + drc_processing_mode(pStatic, + *pDrcLibMem->drc_feature_mode_ptr, + (drc_channel_linking_t)pDrcLibMem->drc_config_ptr->channelLinked, + pDrcLibMem->drc_state_struct_ptr); + } + else // + { + + return DRC_MEMERROR; + } + + break; + } + case DRC_PARAM_CONFIG: + { + // copy only when mem size matches to what is allocated in the lib memory + if (memSize == sizeof(drc_config_t)) + { + + // set the calibration params in the lib memory + *pDrcLibMem->drc_config_ptr = *(drc_config_t *)pMem; + + // update drc processing mode + drc_processing_mode(pStatic, + *pDrcLibMem->drc_feature_mode_ptr, + (drc_channel_linking_t)pDrcLibMem->drc_config_ptr->channelLinked, + pDrcLibMem->drc_state_struct_ptr); + + // if at least one of the tuning params which determine output RMS thresholds gets updated above, + // re-calculate output RMS thresholds again + if (output_rms_comp(pDrcLibMem) != DRC_SUCCESS) + { + + return DRC_FAILURE; + } + } + else // + { + + return DRC_MEMERROR; + } + + break; + } + case DRC_PARAM_SET_RESET: + { + // Reset internal states(flush memory) here; wrapper no need to provide memory space for doing this + if (pMem == NULL && memSize == 0) + { + uint32 channel; + + if (state_memory_defaults(pDrcLib) != DRC_SUCCESS) + { + + return DRC_FAILURE; + } + + pState->inputIndex = pStatic->delay; + pState->processIndex = 0; // Initialize the current index of the delay buffer + pState->downSampleCounter = 0; // Reset the down sample counter + + // initialize dwcomp_state_change and uwcomp_state_change to 1 for each channel + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + { + pState->dwcomp_state_change[channelNo] = 1; + pState->uwcomp_state_change[channelNo] = 1; + pState->dnexpa_state_change[channelNo] = 1; + } + + // clean up the delay buffer + if (BITS_16 == pStatic->data_width) + { + for (channel = 0; channel < pStatic->num_channel; channel++) + { + memset(pState->delayBuffer[channel], + 0, + (size_t)s64_shl_s64(s64_add_s32_u32(ONE, pStatic->delay), ONE)); + } + } + else + { + for (channel = 0; channel < pStatic->num_channel; channel++) + { + memset(pState->delayBuffer[channel], + 0, + (size_t)s64_shl_s64(s64_add_s32_u32(ONE, pStatic->delay), TWO)); + } + } + } + else + { + return DRC_MEMERROR; + } + + break; + } + + default: + { + return DRC_FAILURE; + } + } + + return DRC_SUCCESS; +} + +/*====================================================================== + +FUNCTION drc_process + +DESCRIPTION Process de-interleaved multi-channel input audio signal +sample by sample. The input can be in any sampling rate +- 8, 16, 22.05, 32, 44.1, 48 KHz. If the input is 16-bit +Q15 and the output is also in the form of 16-bit Q15. If +the input is 32-bit Q27, the output is also in the form of 32-bit Q27. + +DEPENDENCIES Input pointers must not be NULL. + +PARAMETERS pDrcLib: [in] Pointer to lib structure +pOutPtr: [out] Pointer to de-interleaved multi - channel output PCM samples +pInPtr: [in] Pointer to de-interleaved multi - channel input PCM samples +nSamplePerChannel: [in] Number of samples to be processed per channel + +SIDE EFFECTS None. + +======================================================================*/ + +DRC_RESULT drc_process(drc_lib_t *pDrcLib, int8 **pOutPtr, int8 **pInPtr, uint32 nSamplePerChannel) +{ + drc_lib_mem_t * pDrcLibMem = (drc_lib_mem_t *)pDrcLib->lib_mem_ptr; + drc_static_struct_t *pStatic = pDrcLibMem->drc_static_struct_ptr; + drc_state_struct_t * pState = pDrcLibMem->drc_state_struct_ptr; + drc_config_t * drc_config_ptr = pDrcLibMem->drc_config_ptr; + + //-------------------- variable declarations ----------------------------- + uint16 negativeDrcTavUL16Q16; + uint32 delayBuffSize; + + delayBuffSize = (uint32)s64_add_s32_u32(ONE, pStatic->delay); + // for unsigned Q16, (1-TAV) equals to -TAV + negativeDrcTavUL16Q16 = s16_neg_s16_sat(drc_config_ptr->rmsTavUL16Q16); + + // switching between drc processing modes (Ying) + switch (pState->drcProcessMode) + { + case ProcessMultiChan16Linked: + ProcessMC16Linked(pDrcLib, negativeDrcTavUL16Q16, nSamplePerChannel, delayBuffSize, pOutPtr, pInPtr); + break; + + case ProcessMultiChan16Unlinked: + ProcessMC16Unlinked(pDrcLib, negativeDrcTavUL16Q16, nSamplePerChannel, delayBuffSize, pOutPtr, pInPtr); + break; + + case ProcessMultiChan32Linked: + ProcessMC32Linked(pDrcLib, negativeDrcTavUL16Q16, nSamplePerChannel, delayBuffSize, pOutPtr, pInPtr); + break; + + case ProcessMultiChan32Unlinked: + ProcessMC32Unlinked(pDrcLib, negativeDrcTavUL16Q16, nSamplePerChannel, delayBuffSize, pOutPtr, pInPtr); + break; + + case ProcessOneChan16: + ProcessMono16(pDrcLib, negativeDrcTavUL16Q16, nSamplePerChannel, delayBuffSize, pOutPtr, pInPtr); + break; + + case ProcessOneChan32: + ProcessMono32(pDrcLib, negativeDrcTavUL16Q16, nSamplePerChannel, delayBuffSize, pOutPtr, pInPtr); + break; + + case ProcessBypass16: + ProcessBP16(pState, pStatic, nSamplePerChannel, delayBuffSize, pOutPtr, pInPtr); + break; + + case ProcessBypass32: + ProcessBP32(pState, pStatic, nSamplePerChannel, delayBuffSize, pOutPtr, pInPtr); + break; + + default: + + return DRC_FAILURE; + } + + return DRC_SUCCESS; +} + +/*====================================================================== + +FUNCTION drc_processing_mode + +DESCRIPTION Checks on the static/calib parameters to determine DRC processing mode + +PARAMETERS pStatic: [in] pointer to the config structure +mode: [in] Bypass or DRC mode +link: [in] channel linked or unlink +state: [out] pointer to the state structure that saves the DRC processing states + +RETURN VALUE Failure or Success + +SIDE EFFECTS None. + +======================================================================*/ +DRC_RESULT drc_processing_mode(drc_static_struct_t * pStatic, + drc_feature_mode_t mode, + drc_channel_linking_t link, + drc_state_struct_t * state) +{ + + // DRC process mode determination to avoid checks in the process function (Ying) + if ((drc_feature_mode_t)DRC_BYPASSED == mode) // DRC Bypass mode; + { + if (BITS_16 == pStatic->data_width) // 16bit + { + state->drcProcessMode = ProcessBypass16; + } + else // 32bit + { + state->drcProcessMode = ProcessBypass32; + } + } + else // DRC processing mode + { + if (1 == pStatic->num_channel) // mono + { + if (BITS_16 == pStatic->data_width) // 16bit + { + state->drcProcessMode = ProcessOneChan16; + } + else // 32bit + { + state->drcProcessMode = ProcessOneChan32; + } + } + else // multichannel + { + if (BITS_16 == pStatic->data_width) // 16bit + { + if (link == CHANNEL_LINKED) // linked + { + state->drcProcessMode = ProcessMultiChan16Linked; + } + else // unlinked + { + state->drcProcessMode = ProcessMultiChan16Unlinked; + } + } + else // 32bit + { + if (link == CHANNEL_LINKED) // linked + { + state->drcProcessMode = ProcessMultiChan32Linked; + } + else + { + state->drcProcessMode = ProcessMultiChan32Unlinked; + } + } + } + } + + return DRC_SUCCESS; +} + +#ifdef DRCLIB_ORIGINAL + +/*====================================================================== + +FUNCTION ProcessBP16 + +DESCRIPTION DRC processing for 16bit Bypass mode + +PARAMETERS pState: [in] pointer to the state structure + pStatic: [in] pointer to the config structure + nSamplePerChannel: [in] number of samples per channel + delayBuffSize: [in] size of delay buffer + pOutPtr: [out] pointer to the output data + pInPtr: [in] pointer to the input data + +RETURN VALUE Failure or Success + +SIDE EFFECTS None. + +======================================================================*/ +DRC_RESULT ProcessBP16(drc_state_struct_t * pState, + drc_static_struct_t *pStatic, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr) +{ + uint32 i, channelNo; + + for (i = 0; i < nSamplePerChannel; i++) + { + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + { + ((int16 *)pState->delayBuffer[channelNo])[pState->inputIndex] = ((int16 *)pInPtr[channelNo])[i]; + ((int16 *)(pOutPtr[channelNo]))[i] = ((int16 *)pState->delayBuffer[channelNo])[pState->processIndex]; + } + + // Check if Delay buffer reaches the cirulary bundary + pState->processIndex++; + pState->inputIndex++; + + pState->processIndex = s32_modwrap_s32_u32(pState->processIndex, delayBuffSize); + pState->inputIndex = s32_modwrap_s32_u32(pState->inputIndex, delayBuffSize); + } + + return DRC_SUCCESS; +} + +/*====================================================================== + +FUNCTION ProcessBP32 + +DESCRIPTION DRC processing for 32bit Bypass mode + +PARAMETERS pState: [in] pointer to the state structure + pStatic: [in] pointer to the config structure + nSamplePerChannel: [in] number of samples per channel + delayBuffSize: [in] size of delay buffer + pOutPtr: [out] pointer to the output data + pInPtr: [in] pointer to the input data + +RETURN VALUE Failure or Success + +SIDE EFFECTS None. + +======================================================================*/ + +DRC_RESULT ProcessBP32(drc_state_struct_t * pState, + drc_static_struct_t *pStatic, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr) +{ + uint32 i, channelNo; + + for (i = 0; i < nSamplePerChannel; i++) + { + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + { + ((int32 *)pState->delayBuffer[channelNo])[pState->inputIndex] = ((int32 *)pInPtr[channelNo])[i]; + ((int32 *)(pOutPtr[channelNo]))[i] = ((int32 *)pState->delayBuffer[channelNo])[pState->processIndex]; + } + + // Check if Delay buffer reaches the cirulary bundary + pState->processIndex++; + pState->inputIndex++; + + pState->processIndex = s32_modwrap_s32_u32(pState->processIndex, delayBuffSize); + pState->inputIndex = s32_modwrap_s32_u32(pState->inputIndex, delayBuffSize); + } + + return DRC_SUCCESS; +} + +/*====================================================================== + +FUNCTION ProcessMono16 + +DESCRIPTION DRC processing for 16bit single channel case + +PARAMETERS pDrcLib: [in] pointer to the library structure + negativeDrcTavUL16Q16: [in] precalculated negative time constant + nSamplePerChannel: [in] number of samples per channel + delayBuffSize: [in] size of delay buffer + pOutPtr: [out] pointer to the output data + pInPtr: [in] pointer to the input data + +RETURN VALUE Failure or Success + +SIDE EFFECTS None. + +======================================================================*/ + +DRC_RESULT ProcessMono16(drc_lib_t *pDrcLib, + uint16 negativeDrcTavUL16Q16, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr) +{ + uint32 i; + int16 *output; + int16 processData; + int16 *delayBuffer; + int32 tempL32; + int64 tempOutL64; + uint32 gainNum = 1; + int32 currDrcRmsL32; + + drc_lib_mem_t * pDrcLibMem = (drc_lib_mem_t *)pDrcLib->lib_mem_ptr; + drc_state_struct_t *pState = pDrcLibMem->drc_state_struct_ptr; + drc_config_t * drc_config_ptr = pDrcLibMem->drc_config_ptr; + + for (i = 0; i < nSamplePerChannel; i++) + { + ((int16 *)pState->delayBuffer[0])[pState->inputIndex] = ((int16 *)pInPtr[0])[i]; + + //------------ Compute long term RMS of DRC for mono case: ------------ + delayBuffer = (int16 *)pState->delayBuffer[0]; + currDrcRmsL32 = s32_mult_s16_s16(delayBuffer[pState->inputIndex], delayBuffer[pState->inputIndex]); + + // current_drcRms = previous_drcRms*(1-drcTav) + drcTav * x[n] ^ 2 + pState->rmsStateL32[0] = + s32_extract_s64_l(s64_add_s64_s64(s64_mult_s32_u16_shift(currDrcRmsL32, drc_config_ptr->rmsTavUL16Q16, 0), + s64_mult_s32_u16_shift(pState->rmsStateL32[0], negativeDrcTavUL16Q16, 0))); + + // compute the current INPUT RMS in dB (log domain) + // log10_fixed: input L32Q0 format, output Q23 format + if (pState->rmsStateL32[0] != 0) + { + pState->drcRmsDBL32Q23[0] = log10_fixed(pState->rmsStateL32[0]); // log10_fixed is 10*log10(.) in fixed point + } + else + { + pState->drcRmsDBL32Q23[0] = MIN_RMS_DB_L32Q23; + } + + // ----------- Compute DRC gain ------------------------ + compute_drc_gain(pState, drc_config_ptr, gainNum); + + // ----------- Apply DRC gain -------------------------- + // delayBuffer = (int16 *) (pState->delayBuffer[0]); + processData = delayBuffer[pState->processIndex]; + // apply gain and output has same QFactor as input + tempL32 = s32_saturate_s64( + s64_shl_s64(s64_add_s64_s32(s64_mult_u32_s16(pState->gainUL32Q15[0], processData), 0x4000), -15)); + + if (drc_config_ptr->makeupGainUL16Q12 != MAKEUPGAIN_UNITY) // Implement only non-unity gain + { + // Multiply output with the shift normalized makeup gain + tempOutL64 = + s64_shl_s64(s64_add_s64_s32(s64_mult_s32_u16(tempL32, drc_config_ptr->makeupGainUL16Q12), 0x800), -12); + tempL32 = s32_saturate_s64(tempOutL64); + } + + // output results + output = (int16 *)(pOutPtr[0]); + output[i] = s16_saturate_s32(tempL32); + + // Check if Delay buffer reaches the cirulary bundary + pState->processIndex++; + pState->inputIndex++; + + pState->processIndex = s32_modwrap_s32_u32(pState->processIndex, delayBuffSize); + pState->inputIndex = s32_modwrap_s32_u32(pState->inputIndex, delayBuffSize); + } + + return DRC_SUCCESS; +} + +/*====================================================================== + +FUNCTION ProcessMono32 + +DESCRIPTION DRC processing for 32bit single channel case + +PARAMETERS pDrcLib: [in] pointer to the library structure + negativeDrcTavUL16Q16: [in] precalculated negative time constant + nSamplePerChannel: [in] number of samples per channel + delayBuffSize: [in] size of delay buffer + pOutPtr: [out] pointer to the output data + pInPtr: [in] pointer to the input data + +RETURN VALUE Failure or Success + +SIDE EFFECTS None. + +======================================================================*/ + +DRC_RESULT ProcessMono32(drc_lib_t *pDrcLib, + uint16 negativeDrcTavUL16Q16, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr) +{ + uint32 i; + int32 *output; + int32 processData; + int32 *delayBuffer; + int32 tempL32; + int64 currDrcRmsL64, tempOutL64; + uint32 gainNum = 1; + int32 currDrcRmsL32; + + drc_lib_mem_t * pDrcLibMem = (drc_lib_mem_t *)pDrcLib->lib_mem_ptr; + drc_state_struct_t *pState = pDrcLibMem->drc_state_struct_ptr; + drc_config_t * drc_config_ptr = pDrcLibMem->drc_config_ptr; + + for (i = 0; i < nSamplePerChannel; i++) + { + + ((int32 *)pState->delayBuffer[0])[pState->inputIndex] = ((int32 *)pInPtr[0])[i]; + //-------- Compute long term input RMS for DRC --------- + delayBuffer = (int32 *)pState->delayBuffer[0]; + // x[n] ^ 2 + currDrcRmsL64 = s64_mult_s32_s32(delayBuffer[pState->inputIndex], delayBuffer[pState->inputIndex]); + // Right shift to get to the same Q-factor as in the 16-bits data width case. + currDrcRmsL32 = s32_saturate_s64(s64_shl_s64(currDrcRmsL64, s16_shl_s16(s16_sub_s16_s16(Q15, Q27), ONE))); + + // current_drcRms = previous_drcRms*(1-drcTav) + drcTav * x[n] ^ 2 + pState->rmsStateL32[0] = + s32_extract_s64_l(s64_add_s64_s64(s64_mult_s32_u16_shift(currDrcRmsL32, drc_config_ptr->rmsTavUL16Q16, 0), + s64_mult_s32_u16_shift(pState->rmsStateL32[0], negativeDrcTavUL16Q16, 0))); + + // compute the current INPUT RMS in dB (log domain) + // log10_fixed: input L32Q0 format, output Q23 format + if (pState->rmsStateL32[0] != 0) + { + pState->drcRmsDBL32Q23[0] = log10_fixed(pState->rmsStateL32[0]); // log10_fixed is 10*log10(.) in fixed point + } + else + { + pState->drcRmsDBL32Q23[0] = MIN_RMS_DB_L32Q23; + } + + // ---------- Compute DRC gain ------------------- + compute_drc_gain(pState, drc_config_ptr, gainNum); + + // ---------- Apply DRC gain ---------------------- + delayBuffer = (int32 *)(pState->delayBuffer[0]); + processData = delayBuffer[pState->processIndex]; + + // apply gain and output has same QFactor as input + tempOutL64 = s64_shl_s64(s64_add_s64_s32(s64_mult_s32_u32(processData, pState->gainUL32Q15[0]), 0x4000), -15); + tempL32 = s32_saturate_s64(tempOutL64); + + // apply make up gain if needed + if (drc_config_ptr->makeupGainUL16Q12 != MAKEUPGAIN_UNITY) // Implement only non-unity gain + { + // Multiply output with the shift normalized makeup gain + tempOutL64 = + s64_shl_s64(s64_add_s64_s32(s64_mult_s32_u16(tempL32, drc_config_ptr->makeupGainUL16Q12), 0x800), -12); + tempL32 = s32_saturate_s64(tempOutL64); + } + + // output results + output = (int32 *)(pOutPtr[0]); + output[i] = tempL32; + + // Check if Delay buffer reaches the cirulary bundary + pState->processIndex++; + pState->inputIndex++; + + pState->processIndex = s32_modwrap_s32_u32(pState->processIndex, delayBuffSize); + pState->inputIndex = s32_modwrap_s32_u32(pState->inputIndex, delayBuffSize); + } + + return DRC_SUCCESS; +} + +/*====================================================================== + +FUNCTION ProcessMC16Linked + +DESCRIPTION DRC processing for 16bit multichannel linked case + +PARAMETERS pDrcLib: [in] pointer to the library structure + negativeDrcTavUL16Q16: [in] precalculated negative time constant + nSamplePerChannel: [in] number of samples per channel + delayBuffSize: [in] size of delay buffer + pOutPtr: [out] pointer to the output data + pInPtr: [in] pointer to the input data + +RETURN VALUE Failure or Success + +SIDE EFFECTS None. + +======================================================================*/ + +DRC_RESULT ProcessMC16Linked(drc_lib_t *pDrcLib, + uint16 negativeDrcTavUL16Q16, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr) +{ + + uint32 i, channelNo; + int16 *output; + int16 processData; + int16 *delayBuffer; + int32 tempL32[MAX_NUM_CHANNEL]; + int64 tempOutL64; + int32 currDrcRmsL32[MAX_NUM_CHANNEL]; + int64 squareInputL64; + uint32 gainNum = 1; + + drc_lib_mem_t * pDrcLibMem = (drc_lib_mem_t *)pDrcLib->lib_mem_ptr; + drc_state_struct_t * pState = pDrcLibMem->drc_state_struct_ptr; + drc_static_struct_t *pStatic = pDrcLibMem->drc_static_struct_ptr; + drc_config_t * drc_config_ptr = pDrcLibMem->drc_config_ptr; + + for (i = 0; i < nSamplePerChannel; i++) + { + squareInputL64 = 0; + + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + ((int16 *)pState->delayBuffer[channelNo])[pState->inputIndex] = ((int16 *)pInPtr[channelNo])[i]; + + //-------- Compute long term input RMS for DRC --------- + // For stereo linked, currDrcRms = (xL(n)^2 + xR(n)^2)/2 (stereo channel example) + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + { + delayBuffer = (int16 *)pState->delayBuffer[channelNo]; + squareInputL64 = s64_add_s64_s64(squareInputL64, + s64_mult_s16_s16_shift(delayBuffer[pState->inputIndex], + delayBuffer[pState->inputIndex], + 0)); + } + + // num_channel must be checked during get_mem() and init_mem(); + // Since there is no need to check the range of num_channel here; + // Can safely assume num_channel is valid; + + // calculate squareInputL64 = squareInputL64/pDrcLib->drc_static_struct.num_channel + switch (pStatic->num_channel) + { + case 2: + squareInputL64 = s64_shl_s64(squareInputL64, MINUS_ONE); // squareInputL64 >>= 1; + break; + case 3: + // to do: squareInputL64 = (squareInputL64 * ONE_BY_3_Q15) >> Q15. But, need to scale down squareInputL64 + // before multiply since it is int64 to simplify shifting, scale down squareInputL64 by the same amount as + // Q15 ( (squareInputL64 >> Q15)*ONE_BY_3_Q15 << Q15 ) >> Q15 => (squareInputL64 >> Q15)*ONE_BY_3_Q15 + squareInputL64 = + s64_mult_s32_s16(s32_saturate_s64(s64_shl_s64(squareInputL64, MINUS_FIFTEEN)), ONE_BY_3_Q15); + break; + case 4: + squareInputL64 = s64_shl_s64(squareInputL64, MINUS_TWO); // squareInputL64 >>= 2; + break; + case 5: + // to do: squareInputL64 = (squareInputL64 * ONE_BY_5_Q15) >> Q15. But, need to scale down squareInputL64 + // before multiply since it is int64 to simplify shifting, scale down squareInputL64 by the same amount as + // Q15 ( (squareInputL64 >> Q15)*ONE_BY_5_Q15 << Q15 ) >> Q15 => (squareInputL64 >> Q15)*ONE_BY_5_Q15 + squareInputL64 = + s64_mult_s32_s16(s32_saturate_s64(s64_shl_s64(squareInputL64, MINUS_FIFTEEN)), ONE_BY_5_Q15); + break; + case 6: + // to do: squareInputL64 = (squareInputL64 * ONE_BY_6_Q15) >> Q15. But, need to scale down squareInputL64 + // before multiply since it is int64 to simplify shifting, scale down squareInputL64 by the same amount as + // Q15 ( (squareInputL64 >> Q15)*ONE_BY_6_Q15 << Q15 ) >> Q15 => (squareInputL64 >> Q15)*ONE_BY_6_Q15 + squareInputL64 = + s64_mult_s32_s16(s32_saturate_s64(s64_shl_s64(squareInputL64, MINUS_FIFTEEN)), ONE_BY_6_Q15); + break; + case 7: + // to do: squareInputL64 = (squareInputL64 * ONE_BY_7_Q15) >> Q15. But, need to scale down squareInputL64 + // before multiply since it is int64 to simplify shifting, scale down squareInputL64 by the same amount as + // Q15 ( (squareInputL64 >> Q15)*ONE_BY_7_Q15 << Q15 ) >> Q15 => (squareInputL64 >> Q15)*ONE_BY_7_Q15 + squareInputL64 = + s64_mult_s32_s16(s32_saturate_s64(s64_shl_s64(squareInputL64, MINUS_FIFTEEN)), ONE_BY_7_Q15); + break; + case 8: + squareInputL64 = s64_shl_s64(squareInputL64, MINUS_THREE); // squareInputL64 >>= 3; + break; + case 16: + squareInputL64 = s64_shl_s64(squareInputL64, MINUS_FOUR); // squareInputL64 >>= 4; + break; + case 32: + squareInputL64 = s64_shl_s64(squareInputL64, MINUS_FIVE); // squareInputL64 >>= 5; + break; + default: + // Default case is for remaining no. of channels till 32 + squareInputL64 = s64_mult_s32_s16(s32_saturate_s64(s64_shl_s64(squareInputL64, MINUS_FIFTEEN)), + rms_constant_factor[pStatic->num_channel]); + break; + } + currDrcRmsL32[0] = s32_saturate_s64(squareInputL64); + + // current_drcRms = previous_drcRms*(1-drcTav) + drcTav * x[n] ^ 2 + pState->rmsStateL32[0] = + s32_extract_s64_l(s64_add_s64_s64(s64_mult_s32_u16_shift(currDrcRmsL32[0], drc_config_ptr->rmsTavUL16Q16, 0), + s64_mult_s32_u16_shift(pState->rmsStateL32[0], negativeDrcTavUL16Q16, 0))); + + // compute the current INPUT RMS in dB (log domain) + // log10_fixed: input L32Q0 format, output Q23 format + if (pState->rmsStateL32[0] != 0) + { + pState->drcRmsDBL32Q23[0] = log10_fixed(pState->rmsStateL32[0]); // log10_fixed is 10*log10(.) in fixed point + } + else + { + pState->drcRmsDBL32Q23[0] = MIN_RMS_DB_L32Q23; + } + + // ---------- Compute DRC gain ------------------- + compute_drc_gain(pState, drc_config_ptr, gainNum); + + // ---------- Apply DRC gain ---------------------- + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + { + delayBuffer = (int16 *)(pState->delayBuffer[channelNo]); + processData = delayBuffer[pState->processIndex]; + + pState->currState[channelNo] = pState->currState[0]; // use same currState for all channels + pState->gainUL32Q15[channelNo] = pState->gainUL32Q15[0]; // use same gain for all channels + // apply gain and output has same QFactor as input + tempL32[channelNo] = s32_saturate_s64( + s64_shl_s64(s64_add_s64_s32(s64_mult_u32_s16(pState->gainUL32Q15[channelNo], processData), 0x4000), -15)); + } + + // apply make up gain if needed + if (drc_config_ptr->makeupGainUL16Q12 != MAKEUPGAIN_UNITY) // Implement only non-unity gain + { + + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + { + // Multiply output with the shift normalized makeup gain + tempOutL64 = + s64_shl_s64(s64_add_s64_s32(s64_mult_s32_u16(tempL32[channelNo], drc_config_ptr->makeupGainUL16Q12), + 0x800), + -12); + tempL32[channelNo] = s32_saturate_s64(tempOutL64); + } + } + + // output results + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + { + output = (int16 *)(pOutPtr[channelNo]); + output[i] = s16_saturate_s32(tempL32[channelNo]); + } + + // Check if Delay buffer reaches the cirulary bundary + pState->processIndex++; + pState->inputIndex++; + + pState->processIndex = s32_modwrap_s32_u32(pState->processIndex, delayBuffSize); + pState->inputIndex = s32_modwrap_s32_u32(pState->inputIndex, delayBuffSize); + } + + return DRC_SUCCESS; +} + +/*====================================================================== + +FUNCTION ProcessMC16Unlinked + +DESCRIPTION DRC processing for 16bit multichannel unlinked case + +PARAMETERS pDrcLib: [in] pointer to the library structure + negativeDrcTavUL16Q16: [in] precalculated negative time constant + nSamplePerChannel: [in] number of samples per channel + delayBuffSize: [in] size of delay buffer + pOutPtr: [out] pointer to the output data + pInPtr: [in] pointer to the input data + +RETURN VALUE Failure or Success + +SIDE EFFECTS None. + +======================================================================*/ + +DRC_RESULT ProcessMC16Unlinked(drc_lib_t *pDrcLib, + uint16 negativeDrcTavUL16Q16, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr) +{ + + uint32 i, gainNum, channelNo; + int16 *output; + int16 processData; + int16 *delayBuffer; + int32 tempL32[MAX_NUM_CHANNEL]; + int64 tempOutL64; + int32 currDrcRmsL32[MAX_NUM_CHANNEL]; + + drc_lib_mem_t * pDrcLibMem = (drc_lib_mem_t *)pDrcLib->lib_mem_ptr; + drc_state_struct_t * pState = pDrcLibMem->drc_state_struct_ptr; + drc_static_struct_t *pStatic = pDrcLibMem->drc_static_struct_ptr; + drc_config_t * drc_config_ptr = pDrcLibMem->drc_config_ptr; + + for (i = 0; i < nSamplePerChannel; i++) + { + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + ((int16 *)pState->delayBuffer[channelNo])[pState->inputIndex] = ((int16 *)pInPtr[channelNo])[i]; + + //-------- Compute long term input RMS for DRC --------- + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + { + delayBuffer = (int16 *)pState->delayBuffer[channelNo]; + // x[n] ^ 2 for each channel separately + currDrcRmsL32[channelNo] = s32_mult_s16_s16(delayBuffer[pState->inputIndex], delayBuffer[pState->inputIndex]); + } + + gainNum = pStatic->num_channel; + + // current_drcRms = previous_drcRms*(1-drcTav) + drcTav * x[n] ^ 2 + for (channelNo = 0; channelNo < gainNum; channelNo++) + { + pState->rmsStateL32[channelNo] = s32_extract_s64_l( + s64_add_s64_s64(s64_mult_s32_u16_shift(currDrcRmsL32[channelNo], drc_config_ptr->rmsTavUL16Q16, 0), + s64_mult_s32_u16_shift(pState->rmsStateL32[channelNo], negativeDrcTavUL16Q16, 0))); + + // compute the current INPUT RMS in dB (log domain) + // log10_fixed: input L32Q0 format, output Q23 format + if (pState->rmsStateL32[channelNo] != 0) + { + pState->drcRmsDBL32Q23[channelNo] = + log10_fixed(pState->rmsStateL32[channelNo]); // log10_fixed is 10*log10(.) in fixed point + } + else + { + pState->drcRmsDBL32Q23[channelNo] = MIN_RMS_DB_L32Q23; + } + } + + // ---------- Compute DRC gain ------------------- + compute_drc_gain(pState, drc_config_ptr, gainNum); + + // ---------- Apply DRC gain ---------------------- + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + { + delayBuffer = (int16 *)(pState->delayBuffer[channelNo]); + processData = delayBuffer[pState->processIndex]; + + // apply gain and output has same QFactor as input + tempL32[channelNo] = s32_saturate_s64( + s64_shl_s64(s64_add_s64_s32(s64_mult_u32_s16(pState->gainUL32Q15[channelNo], processData), 0x4000), -15)); + } + // apply make up gain if needed + if (drc_config_ptr->makeupGainUL16Q12 != MAKEUPGAIN_UNITY) // Implement only non-unity gain + { + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + { + // Multiply output with the shift normalized makeup gain + tempOutL64 = + s64_shl_s64(s64_add_s64_s32(s64_mult_s32_u16(tempL32[channelNo], drc_config_ptr->makeupGainUL16Q12), + 0x800), + -12); + tempL32[channelNo] = s32_saturate_s64(tempOutL64); + } + } + + // output results + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + { + output = (int16 *)(pOutPtr[channelNo]); + output[i] = s16_saturate_s32(tempL32[channelNo]); + } + + // Check if Delay buffer reaches the cirulary bundary + pState->processIndex++; + pState->inputIndex++; + + pState->processIndex = s32_modwrap_s32_u32(pState->processIndex, delayBuffSize); + pState->inputIndex = s32_modwrap_s32_u32(pState->inputIndex, delayBuffSize); + } + + return DRC_SUCCESS; +} + +/*====================================================================== + +FUNCTION ProcessMC32Linked + +DESCRIPTION DRC processing for 32bit multichannel linked case + +PARAMETERS pDrcLib: [in] pointer to the library structure + negativeDrcTavUL16Q16: [in] precalculated negative time constant + nSamplePerChannel: [in] number of samples per channel + delayBuffSize: [in] size of delay buffer + pOutPtr: [out] pointer to the output data + pInPtr: [in] pointer to the input data + +RETURN VALUE Failure or Success + +SIDE EFFECTS None. + +======================================================================*/ + +DRC_RESULT ProcessMC32Linked(drc_lib_t *pDrcLib, + uint16 negativeDrcTavUL16Q16, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr) +{ + uint32 i, gainNum, channelNo; + int32 *output; + int32 processData; + int32 *delayBuffer; + int32 tempL32[MAX_NUM_CHANNEL]; + int64 tempOutL64; + int64 squareInputL64; + int64 currDrcRmsL64; + int32 currDrcRmsL32[MAX_NUM_CHANNEL]; + + drc_lib_mem_t * pDrcLibMem = (drc_lib_mem_t *)pDrcLib->lib_mem_ptr; + drc_state_struct_t * pState = pDrcLibMem->drc_state_struct_ptr; + drc_static_struct_t *pStatic = pDrcLibMem->drc_static_struct_ptr; + drc_config_t * drc_config_ptr = pDrcLibMem->drc_config_ptr; + + for (i = 0; i < nSamplePerChannel; i++) + { + squareInputL64 = 0; + + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + ((int32 *)pState->delayBuffer[channelNo])[pState->inputIndex] = ((int32 *)pInPtr[channelNo])[i]; + + //-------- Compute long term input RMS for DRC --------- + // For stereo linked, currDrcRms = (xL(n)^2 + xR(n)^2)/2 (stereo channel example) + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + { + delayBuffer = (int32 *)pState->delayBuffer[channelNo]; + currDrcRmsL64 = s64_mult_s32_s32(delayBuffer[pState->inputIndex], delayBuffer[pState->inputIndex]); + currDrcRmsL64 = s64_shl_s64(currDrcRmsL64, s16_shl_s16(s16_sub_s16_s16(Q15, Q27), ONE)); + squareInputL64 = s64_add_s64_s64(squareInputL64, currDrcRmsL64); + } + + // num_channel must be checked during get_mem() and init_mem(); + // Since there is no need to check the range of num_channel here; + // Can safely assume num_channel is valid; + + // calculate squareInputL64 = squareInputL64/pDrcLib->drc_static_struct.num_channel + switch (pStatic->num_channel) + { + case 2: + squareInputL64 = s64_shl_s64(squareInputL64, MINUS_ONE); // squareInputL64 >>= 1; + break; + case 3: + // to do: squareInputL64 = (squareInputL64 * ONE_BY_3_Q15) >> Q15. But, need to scale down squareInputL64 + // before multiply since it is int64 to simplify shifting, scale down squareInputL64 by the same amount as + // Q15 ( (squareInputL64 >> Q15)*ONE_BY_3_Q15 << Q15 ) >> Q15 => (squareInputL64 >> Q15)*ONE_BY_3_Q15 + squareInputL64 = + s64_mult_s32_s16(s32_saturate_s64(s64_shl_s64(squareInputL64, MINUS_FIFTEEN)), ONE_BY_3_Q15); + break; + case 4: + squareInputL64 = s64_shl_s64(squareInputL64, MINUS_TWO); // squareInputL64 >>= 2; + break; + case 5: + // to do: squareInputL64 = (squareInputL64 * ONE_BY_5_Q15) >> Q15. But, need to scale down squareInputL64 + // before multiply since it is int64 to simplify shifting, scale down squareInputL64 by the same amount as + // Q15 ( (squareInputL64 >> Q15)*ONE_BY_5_Q15 << Q15 ) >> Q15 => (squareInputL64 >> Q15)*ONE_BY_5_Q15 + squareInputL64 = + s64_mult_s32_s16(s32_saturate_s64(s64_shl_s64(squareInputL64, MINUS_FIFTEEN)), ONE_BY_5_Q15); + break; + case 6: + // to do: squareInputL64 = (squareInputL64 * ONE_BY_6_Q15) >> Q15. But, need to scale down squareInputL64 + // before multiply since it is int64 to simplify shifting, scale down squareInputL64 by the same amount as + // Q15 ( (squareInputL64 >> Q15)*ONE_BY_6_Q15 << Q15 ) >> Q15 => (squareInputL64 >> Q15)*ONE_BY_6_Q15 + squareInputL64 = + s64_mult_s32_s16(s32_saturate_s64(s64_shl_s64(squareInputL64, MINUS_FIFTEEN)), ONE_BY_6_Q15); + break; + case 7: + // to do: squareInputL64 = (squareInputL64 * ONE_BY_7_Q15) >> Q15. But, need to scale down squareInputL64 + // before multiply since it is int64 to simplify shifting, scale down squareInputL64 by the same amount as + // Q15 ( (squareInputL64 >> Q15)*ONE_BY_7_Q15 << Q15 ) >> Q15 => (squareInputL64 >> Q15)*ONE_BY_7_Q15 + squareInputL64 = + s64_mult_s32_s16(s32_saturate_s64(s64_shl_s64(squareInputL64, MINUS_FIFTEEN)), ONE_BY_7_Q15); + break; + case 8: + squareInputL64 = s64_shl_s64(squareInputL64, MINUS_THREE); // squareInputL64 >>= 3; + break; + case 16: + squareInputL64 = s64_shl_s64(squareInputL64, MINUS_FOUR); // squareInputL64 >>= 4; + break; + case 32: + squareInputL64 = s64_shl_s64(squareInputL64, MINUS_FIVE); // squareInputL64 >>= 5; + break; + default: + // Default case is for remaining no. of channels till 32 + squareInputL64 = s64_mult_s32_s16(s32_saturate_s64(s64_shl_s64(squareInputL64, MINUS_FIFTEEN)), + rms_constant_factor[pStatic->num_channel]); + break; + } + currDrcRmsL32[0] = s32_saturate_s64(squareInputL64); + + gainNum = 1; + + // current_drcRms = previous_drcRms*(1-drcTav) + drcTav * x[n] ^ 2 + for (channelNo = 0; channelNo < gainNum; channelNo++) + { + pState->rmsStateL32[channelNo] = s32_extract_s64_l( + s64_add_s64_s64(s64_mult_s32_u16_shift(currDrcRmsL32[channelNo], drc_config_ptr->rmsTavUL16Q16, 0), + s64_mult_s32_u16_shift(pState->rmsStateL32[channelNo], negativeDrcTavUL16Q16, 0))); + + // compute the current INPUT RMS in dB (log domain) + // log10_fixed: input L32Q0 format, output Q23 format + if (pState->rmsStateL32[channelNo] != 0) + { + pState->drcRmsDBL32Q23[channelNo] = + log10_fixed(pState->rmsStateL32[channelNo]); // log10_fixed is 10*log10(.) in fixed point + } + else + { + pState->drcRmsDBL32Q23[channelNo] = MIN_RMS_DB_L32Q23; + } + } + + // ---------- Compute DRC gain ------------------- + compute_drc_gain(pState, drc_config_ptr, gainNum); + + // ---------- Apply DRC gain ---------------------- + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + { + delayBuffer = (int32 *)(pState->delayBuffer[channelNo]); + processData = delayBuffer[pState->processIndex]; + + pState->currState[channelNo] = pState->currState[0]; // use same currState for all channels + pState->gainUL32Q15[channelNo] = pState->gainUL32Q15[0]; // use same gain for all channels + // apply gain and output has same QFactor as input + tempOutL64 = + s64_shl_s64(s64_add_s64_s32(s64_mult_s32_u32(processData, pState->gainUL32Q15[channelNo]), 0x4000), -15); + tempL32[channelNo] = s32_saturate_s64(tempOutL64); + } + + // apply make up gain if needed + if (drc_config_ptr->makeupGainUL16Q12 != MAKEUPGAIN_UNITY) // Implement only non-unity gain + { + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + { + // Multiply output with the shift normalized makeup gain + tempOutL64 = + s64_shl_s64(s64_add_s64_s32(s64_mult_s32_u16(tempL32[channelNo], drc_config_ptr->makeupGainUL16Q12), + 0x800), + -12); + tempL32[channelNo] = s32_saturate_s64(tempOutL64); + } + } + + // output results + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + { + output = (int32 *)(pOutPtr[channelNo]); + output[i] = tempL32[channelNo]; + } + + // Check if Delay buffer reaches the cirulary bundary + pState->processIndex++; + pState->inputIndex++; + + pState->processIndex = s32_modwrap_s32_u32(pState->processIndex, delayBuffSize); + pState->inputIndex = s32_modwrap_s32_u32(pState->inputIndex, delayBuffSize); + } + + return DRC_SUCCESS; +} + +/*====================================================================== + +FUNCTION ProcessMC32Unlinked + +DESCRIPTION DRC processing for 32bit multichannel unlinked case + +PARAMETERS pDrcLib: [in] pointer to the library structure + negativeDrcTavUL16Q16: [in] precalculated negative time constant + nSamplePerChannel: [in] number of samples per channel + delayBuffSize: [in] size of delay buffer + pOutPtr: [out] pointer to the output data + pInPtr: [in] pointer to the input data + +RETURN VALUE Failure or Success + +SIDE EFFECTS None. + +======================================================================*/ + +DRC_RESULT ProcessMC32Unlinked(drc_lib_t *pDrcLib, + uint16 negativeDrcTavUL16Q16, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr) +{ + uint32 i, gainNum, channelNo; + int32 *output; + int32 processData; + int32 *delayBuffer; + int32 tempL32[MAX_NUM_CHANNEL]; + int64 tempOutL64; + int64 currDrcRmsL64; + int32 currDrcRmsL32[MAX_NUM_CHANNEL]; + + drc_lib_mem_t * pDrcLibMem = (drc_lib_mem_t *)pDrcLib->lib_mem_ptr; + drc_state_struct_t * pState = pDrcLibMem->drc_state_struct_ptr; + drc_static_struct_t *pStatic = pDrcLibMem->drc_static_struct_ptr; + drc_config_t * drc_config_ptr = pDrcLibMem->drc_config_ptr; + + for (i = 0; i < nSamplePerChannel; i++) + { + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + ((int32 *)pState->delayBuffer[channelNo])[pState->inputIndex] = ((int32 *)pInPtr[channelNo])[i]; + + // -------- Compute long term input RMS for DRC --------- + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + { + delayBuffer = (int32 *)pState->delayBuffer[channelNo]; + // x[n] ^ 2 for each channelNo separately & is same Q-factor as in the 16-bit width case. + currDrcRmsL64 = s64_mult_s32_s32(delayBuffer[pState->inputIndex], delayBuffer[pState->inputIndex]); + currDrcRmsL64 = s64_shl_s64(currDrcRmsL64, s16_shl_s16(s16_sub_s16_s16(Q15, Q27), ONE)); + currDrcRmsL32[channelNo] = s32_saturate_s64(currDrcRmsL64); + } + + gainNum = pStatic->num_channel; + + // current_drcRms = previous_drcRms*(1-drcTav) + drcTav * x[n] ^ 2 + // for(channel = 0; channel < pStatic->num_channel; channel++) + for (channelNo = 0; channelNo < gainNum; channelNo++) + { + pState->rmsStateL32[channelNo] = s32_extract_s64_l( + s64_add_s64_s64(s64_mult_s32_u16_shift(currDrcRmsL32[channelNo], drc_config_ptr->rmsTavUL16Q16, 0), + s64_mult_s32_u16_shift(pState->rmsStateL32[channelNo], negativeDrcTavUL16Q16, 0))); + + // compute the current INPUT RMS in dB (log domain) + // log10_fixed: input L32Q0 format, output Q23 format + if (pState->rmsStateL32[channelNo] != 0) + { + pState->drcRmsDBL32Q23[channelNo] = + log10_fixed(pState->rmsStateL32[channelNo]); // log10_fixed is 10*log10(.) in fixed point + } + else + { + pState->drcRmsDBL32Q23[channelNo] = MIN_RMS_DB_L32Q23; + } + } + + // ---------- Compute DRC gain ------------------- + compute_drc_gain(pState, drc_config_ptr, gainNum); + + // ---------- Apply DRC gain ---------------------- + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + { + delayBuffer = (int32 *)(pState->delayBuffer[channelNo]); + processData = delayBuffer[pState->processIndex]; + + // apply gain and output has same QFactor as input + tempOutL64 = + s64_shl_s64(s64_add_s64_s32(s64_mult_s32_u32(processData, pState->gainUL32Q15[channelNo]), 0x4000), -15); + + tempL32[channelNo] = s32_saturate_s64(tempOutL64); + } + // apply make up gain if needed + if (drc_config_ptr->makeupGainUL16Q12 != MAKEUPGAIN_UNITY) // Implement only non-unity gain + { + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + { + // Multiply output with the shift normalized makeup gain + tempOutL64 = + s64_shl_s64(s64_add_s64_s32(s64_mult_s32_u16(tempL32[channelNo], drc_config_ptr->makeupGainUL16Q12), + 0x800), + -12); + tempL32[channelNo] = s32_saturate_s64(tempOutL64); + } + } + + // output results + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + { + output = (int32 *)(pOutPtr[channelNo]); + output[i] = tempL32[channelNo]; + } + + // Check if Delay buffer reaches the cirulary bundary + pState->processIndex++; + pState->inputIndex++; + + pState->processIndex = s32_modwrap_s32_u32(pState->processIndex, delayBuffSize); + pState->inputIndex = s32_modwrap_s32_u32(pState->inputIndex, delayBuffSize); + } + + return DRC_SUCCESS; +} + +/*====================================================================== +FUNCTION compute_drc_gain + +DESCRIPTION Computes the DRC gain for current input samples + +PARAMETERS pState: [in/out] pointer to the state structure that saves the DRC processing states +drc_config_ptr:[in] Pointer to the calibration structure + +RETURN VALUE Computed drc gain value for current input samples + +SIDE EFFECTS None. + +======================================================================*/ + +void compute_drc_gain(drc_state_struct_t *pState, drc_config_t *drc_config_ptr, uint32 gainNum) +{ + + // ------------ Variable Declaration ------------ + int16 drcRmsDBL16Q7; + int32 drcRmsDBL32Q23; + uint32 j, newTargetGainUL32Q15; + int64 currDrcGainL64Q27, gainDiffL64Q27; + DrcStateType currState; + uint16 tempSlopeUL16Q16; + int16 tempThresholdL16Q7, tempSlopeL16Q8; + int32 newTargetGainL32Q26, newTargetGainL32Q23; + int64 tempRmsDBL40Q23; + + //---------------- Update gain computation only if at downSampleLevels -------- + if (0 == pState->downSampleCounter) + { + pState->downSampleCounter = drc_config_ptr->downSampleLevel; + + //------------- Update gain based on DRC mode ---------------- + j = 0; // channel index + do + { + currState = pState->currState[j]; + + drcRmsDBL32Q23 = pState->drcRmsDBL32Q23[j]; // current INPUT RMS in dB (log domain) + drcRmsDBL16Q7 = s16_extract_s32_h(drcRmsDBL32Q23); // Q7 + drcRmsDBL16Q7 = s16_max_s16_s16(drcRmsDBL16Q7, MIN_RMS_DB_L16Q7); + + // ---- Compute the Target Gain for the current sample based on input RMS ---- + // newTargetGainUL32Q15 = target_gain_comp(drc_config_ptr, drcRmsDBL16Q7); + + // figure out what part of compression curve the rms value is in + if (drcRmsDBL16Q7 > drc_config_ptr->dnCompThresholdL16Q7) + // in Down Dompression + { + tempThresholdL16Q7 = drc_config_ptr->dnCompThresholdL16Q7; + tempSlopeUL16Q16 = drc_config_ptr->dnCompSlopeUL16Q16; + + // newTarget = (dwCompThreshold - Xrms[n]) * dwCompSlopeUL16Q16 + newTargetGainL32Q23 = s32_mult_s16_u16(s16_sub_s16_s16(tempThresholdL16Q7, drcRmsDBL16Q7), + tempSlopeUL16Q16); // Q23 + } + else if (drcRmsDBL16Q7 < drc_config_ptr->dnExpaThresholdL16Q7) + // in Down Expansion + { + tempThresholdL16Q7 = drc_config_ptr->upCompThresholdL16Q7; + tempSlopeUL16Q16 = drc_config_ptr->upCompSlopeUL16Q16; + + // newTarget = (dwExpaThresholdL16Q7 - Xrms[n])*dwExpaSlopeUL16Q16 + ... + // (uwCompThresholdL16Q7 - dwExpaThresholdL16Q7)*uwCompSlopeUL16Q16; + newTargetGainL32Q23 = + s32_mult_s16_u16(s16_sub_s16_s16(tempThresholdL16Q7, drc_config_ptr->dnExpaThresholdL16Q7), + tempSlopeUL16Q16); // Q23 + + tempThresholdL16Q7 = drc_config_ptr->dnExpaThresholdL16Q7; + tempSlopeL16Q8 = drc_config_ptr->dnExpaSlopeL16Q8; + + tempRmsDBL40Q23 = + s64_mult_s16_s16_shift(s16_sub_s16_s16(tempThresholdL16Q7, drcRmsDBL16Q7), tempSlopeL16Q8, 8); + newTargetGainL32Q23 = s32_saturate_s64(s64_add_s64_s32(tempRmsDBL40Q23, newTargetGainL32Q23)); + // L32Q23 + + // Limit the gain reduction in Downward Expander part to be dnExpaMinGainDB + if (newTargetGainL32Q23 < drc_config_ptr->dnExpaMinGainDBL32Q23) + { + newTargetGainL32Q23 = drc_config_ptr->dnExpaMinGainDBL32Q23; + } + } + else if (drcRmsDBL16Q7 < drc_config_ptr->upCompThresholdL16Q7) + // in Up Dompression + { + tempThresholdL16Q7 = drc_config_ptr->upCompThresholdL16Q7; + tempSlopeUL16Q16 = drc_config_ptr->upCompSlopeUL16Q16; + + // newTarget = (uwCompThreshold - Xrms[n]) * uwCompSlopeUL16Q16 + newTargetGainL32Q23 = s32_mult_s16_u16(s16_sub_s16_s16(tempThresholdL16Q7, drcRmsDBL16Q7), + tempSlopeUL16Q16); // Q23 + } + else + { + newTargetGainL32Q23 = 0x00000000; + } + + // calculate new target gain = 10^(new target gain log / 20): input L32Q26, out:L32Q15 + newTargetGainL32Q26 = (int32)s64_mult_s32_s16_shift(newTargetGainL32Q23, ONE_OVER_TWENTY_UQ19, 0); + newTargetGainUL32Q15 = exp10_fixed(newTargetGainL32Q26); // Q15 + pState->targetGainUL32Q15[j] = newTargetGainUL32Q15; + + // --- Find out the appropriate time constant based on input RMS --- + if (drcRmsDBL16Q7 > drc_config_ptr->dnCompThresholdL16Q7) + { + if (newTargetGainUL32Q15 < pState->gainUL32Q15[j]) + { + pState->timeConstantUL32Q31[j] = drc_config_ptr->dnCompAttackUL32Q31; + currState = ATTACK; + } + else if (newTargetGainUL32Q15 >= + (uint32)s32_saturate_s64( + s64_mult_u32_s16_shift(pState->gainUL32Q15[j], drc_config_ptr->dnCompHysterisisUL16Q14, 2))) + { + pState->timeConstantUL32Q31[j] = drc_config_ptr->dnCompReleaseUL32Q31; + currState = RELEASE; + } + else + { + if (currState == ATTACK) + { + pState->timeConstantUL32Q31[j] = 0; + currState = NO_CHANGE; + } + } + + pState->dwcomp_state_change[j] = 0; + pState->dnexpa_state_change[j] = 1; + pState->uwcomp_state_change[j] = 1; + } + else if (drcRmsDBL16Q7 < drc_config_ptr->dnExpaThresholdL16Q7) + { + if ((uint32)s32_saturate_s64( + s64_mult_u32_s16_shift(newTargetGainUL32Q15, drc_config_ptr->dnExpaHysterisisUL16Q14, 2)) < + pState->gainUL32Q15[j]) + { + pState->timeConstantUL32Q31[j] = drc_config_ptr->dnExpaAttackUL32Q31; + currState = ATTACK; + } + else if (newTargetGainUL32Q15 > pState->gainUL32Q15[j]) + { + pState->timeConstantUL32Q31[j] = drc_config_ptr->dnExpaReleaseUL32Q31; + currState = RELEASE; + } + else + { + if (currState == RELEASE) + { + pState->timeConstantUL32Q31[j] = 0; + currState = NO_CHANGE; + } + } + + pState->dnexpa_state_change[j] = 0; + pState->uwcomp_state_change[j] = 1; + pState->dwcomp_state_change[j] = 1; + } + else if (drcRmsDBL16Q7 < drc_config_ptr->upCompThresholdL16Q7) + { + if (newTargetGainUL32Q15 < pState->gainUL32Q15[j]) + { + pState->timeConstantUL32Q31[j] = drc_config_ptr->upCompAttackUL32Q31; + currState = ATTACK; + } + else if (newTargetGainUL32Q15 >= + (uint32)s32_saturate_s64( + s64_mult_u32_s16_shift(pState->gainUL32Q15[j], drc_config_ptr->upCompHysterisisUL16Q14, 2))) + { + pState->timeConstantUL32Q31[j] = drc_config_ptr->upCompReleaseUL32Q31; + currState = RELEASE; + } + else + { + if (currState == ATTACK) + { + pState->timeConstantUL32Q31[j] = 0; + currState = NO_CHANGE; + } + } + + pState->uwcomp_state_change[j] = 0; + pState->dnexpa_state_change[j] = 1; + pState->dwcomp_state_change[j] = 1; + } + else + { + // pState->timeConstantUL32Q31[j] = 0; + currState = NO_CHANGE; + + if (pState->dwcomp_state_change[j] == 0) + { + pState->timeConstantUL32Q31[j] = drc_config_ptr->dnCompReleaseUL32Q31; + currState = RELEASE; + pState->dwcomp_state_change[j] = 1; + } + else if (pState->uwcomp_state_change[j] == 0) + { + pState->timeConstantUL32Q31[j] = drc_config_ptr->upCompAttackUL32Q31; + currState = ATTACK; + pState->uwcomp_state_change[j] = 1; + } + else if (pState->dnexpa_state_change[j] == 0) + { + pState->timeConstantUL32Q31[j] = drc_config_ptr->dnExpaReleaseUL32Q31; + currState = RELEASE; + pState->dnexpa_state_change[j] = 1; + } + } + + // --- calculate DRC gain with determined smooth factor --- + // drcGain = drcGain*(1-timeConstant) + drcTargetGain*timeConstant + // = drcGain + (drcTargetGain - drcGain)*timeConstant + gainDiffL64Q27 = s64_sub_s64_s64(((int64)pState->targetGainUL32Q15[j]) << 12, pState->instGainUL64Q27[j]); + currDrcGainL64Q27 = + s64_mult_s32_u32_shift(s32_saturate_s64(gainDiffL64Q27), pState->timeConstantUL32Q31[j], 1); // Q27 + currDrcGainL64Q27 = s64_add_s64_s64(currDrcGainL64Q27, pState->instGainUL64Q27[j]); + pState->gainUL32Q15[j] = s32_saturate_s64(s64_shl_s64(currDrcGainL64Q27, -12)); + pState->instGainUL64Q27[j] = currDrcGainL64Q27; + pState->currState[j] = currState; + + j++; + + } while (j < gainNum); + } + + pState->downSampleCounter--; +} + +#endif // QDSP6_DRCLIB_ASM diff --git a/modules/processing/gain_control/drc/lib/src/drc_lib.h b/modules/processing/gain_control/drc/lib/src/drc_lib.h new file mode 100644 index 0000000..09afe66 --- /dev/null +++ b/modules/processing/gain_control/drc/lib/src/drc_lib.h @@ -0,0 +1,191 @@ +#ifndef DRCLIB_H +#define DRCLIB_H +/*============================================================================ + @file CDrcLib.h + + Public header file for the Limiter algorithm. + + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause +============================================================================*/ + +/*---------------------------------------------------------------------------- + * Include Files + * -------------------------------------------------------------------------*/ + + +#include "drc_api.h" + +#if ((defined __hexagon__) || (defined __qdsp6__)) +#define QDSP6_DRCLIB_ASM 1 +#elif (defined __XTENSA__) +#define DRCLIB_OPT 1 +#else +#define DRCLIB_ORIGINAL 1 +#endif + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#define DRC_LIB_VER 0X020A020102000000 // 2.10/2.1.2 external(major).external(minor)/major.minor.revision + +static const uint32_t drc_max_stack_size = 2048; +// worst case stack mem consumption in bytes; +// this number is obtained offline via stack profiling +// stack mem consumption should be no bigger than this number + +/*---------------------------------------------------------------------------- +* Constants Definition +* -------------------------------------------------------------------------*/ +#define ONE 1 +#define TWO 2 +//#define THREE 3 +#define MINUS_ONE -1 +#define MINUS_TWO -2 +#define MINUS_THREE -3 +#define MINUS_FOUR -4 +#define MINUS_FIVE -5 +#define ONE_BY_3_Q15 10923 +#define ONE_BY_5_Q15 6554 +#define ONE_BY_6_Q15 5461 +#define ONE_BY_7_Q15 4681 +#define Q15 15 // Q factor for Q15 +#define Q27 27 // Q factor for Q27 +#define MINUS_FIFTEEN -Q15 +#define ALIGN8(o) (((o)+7)&(~7)) + + + +/*---------------------------------------------------------------------------- + * Type Declarations + * -------------------------------------------------------------------------*/ +// internal use only +typedef enum DrcStateType +{ + NO_CHANGE = 0, + ATTACK = 1, + RELEASE = 2 + +} DrcStateType; + + +// internal use only +typedef enum DrcConstants +{ + + ONE_OVER_TWENTY_UQ19 = 26214, + MIN_RMS_DB_L32Q23 = 0, + MIN_RMS_DB_L16Q7 = -728, + DB_16384_L32Q23 = 707051520, + MAKEUPGAIN_UNITY = 4096, +#ifdef PROD_SPECIFIC_MAX_CH + MAX_NUM_CHANNEL = 128 +#else + MAX_NUM_CHANNEL = 32 +#endif + +} DrcConstants; + +// Ying +typedef enum DrcProcessMode +{ + ProcessMultiChan16Linked = 0, + ProcessMultiChan16Unlinked = 1, + ProcessMultiChan32Linked = 2, + ProcessMultiChan32Unlinked = 3, + ProcessOneChan16 = 4, + ProcessOneChan32 = 5, + ProcessBypass16 = 6, + ProcessBypass32 = 7 +} DrcProcessMode; + + +// Default calibration parameters; internal use only +typedef enum DrcParamsDefault +{ + + MODE_DEFAULT = 0x0, // 1 is with DRC processing; 0 is no DRC processing(bypassed, only delay is implemented) + + CHANNEL_LINKED_DEFAULT = 0x0, + DOWNSAMPLE_LEVEL_DEFAULT = 0x1, + + RMS_TAV_DEFAULT = 0x0B78, + MAKEUP_GAIN_DEFAULT = 0x1000, + + DN_EXPA_THRESHOLD_DEFAULT = 0x0A28, + DN_EXPA_SLOPE_DEFAULT = 0xFF9A, + DN_EXPA_ATTACK_DEFAULT = 0x00B3BAB3, + DN_EXPA_RELEASE_DEFAULT = 0x01BF7A00, + DN_EXPA_HYSTERISIS_DEFAULT = 0x49A7, + DN_EXPA_MIN_GAIN_DEFAULT = 0xFD000000, + + UP_COMP_THRESHOLD_DEFAULT = 0x0A28, + UP_COMP_SLOPE_DEFAULT = 0x0, + UP_COMP_ATTACK_DEFAULT = 0x0059FCFC, + UP_COMP_RELEASE_DEFAULT = 0x0059FCFC, + UP_COMP_HYSTERISIS_DEFAULT = 0x49A7, + + DN_COMP_THRESHOLD_DEFAULT = 0x1BA8, + DN_COMP_SLOPE_DEFAULT = 0xF333, + DN_COMP_ATTACK_DEFAULT = 0x06D9931E, + DN_COMP_RELEASE_DEFAULT = 0x00120478, + DN_COMP_HYSTERISIS_DEFAULT = 0x49A7 + +} DrcParamsDefault; + + + +//DRC state params structure +typedef struct drc_state_struct_t +{ + + int8_t** delayBuffer; // dynamic mem allocation for int8_t* delayBuffer[ch_num]; Delay buffer used to save the channel-delayed samples + int32_t* rmsStateL32; // dynamic mem allocation for int32_t rmsStateL32[ch_num]; Current sample index + int32_t* drcRmsDBL32Q23; // dynamic mem allocation for int32_t drcRmsDBL32Q23[ch_num]; + uint32_t* targetGainUL32Q15; // dynamic mem allocation for uint32_t targetGainUL32Q15[ch_num]; + uint32_t* gainUL32Q15; // dynamic mem allocation for uint32_t gainUL32Q15[ch_num]; + DrcStateType* currState; // dynamic mem allocation for DrcStateType currState[ch_num]; + uint32_t* timeConstantUL32Q31; // dynamic mem allocation for uint32_t timeConstantUL32Q31[ch_num]; + int32_t* dwcomp_state_change; // dynamic mem allocation for int32_t* dwcomp_state_change[ch_num] + int32_t* uwcomp_state_change; // dynamic mem allocation for int32_t* uwcomp_state_change[ch_num] + int32_t* dnexpa_state_change; // dynamic mem allocation for int32_t* dnexpa_state_change[ch_num] + uint64_t* instGainUL64Q27; // dynamic mem allocation for uint64_t instGainUL64Q27[ch_num]; + + uint32_t inputIndex; // Current input data index in the delay buffer + uint32_t processIndex; // Current process data index in the delay buffer + uint32_t drcProcessMode; // drc mode for different types of process() Ying + + + int16_t downSampleCounter; + + int16_t outDnCompThresholdL16Q7; + int16_t outDnExpaThresholdL16Q7; + int16_t outUpCompThresholdL16Q7; + +} drc_state_struct_t; + + +// DRC lib mem structure +typedef struct drc_lib_mem_t +{ + drc_static_struct_t* drc_static_struct_ptr; // ptr to the static struct in mem + int32_t drc_static_struct_size; // size of the allocated mem pointed by the static struct + drc_feature_mode_t* drc_feature_mode_ptr; + int32_t drc_feature_mode_size; + drc_config_t* drc_config_ptr; + int32_t drc_config_size; + drc_state_struct_t* drc_state_struct_ptr; // ptr to the state struct in lib mem + int32_t drc_state_struct_size; // size of the allocated mem pointed by the state struct + +} drc_lib_mem_t; + + + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* #ifndef DRCLIB_H */ diff --git a/modules/processing/gain_control/drc/lib/src/drc_lib_opt.c b/modules/processing/gain_control/drc/lib/src/drc_lib_opt.c new file mode 100644 index 0000000..75dd225 --- /dev/null +++ b/modules/processing/gain_control/drc/lib/src/drc_lib_opt.c @@ -0,0 +1,1147 @@ +/* ========================================================================= + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause + ========================================================================== */ + +/* ========================================================================= + FILE NAME: drc_lib_asm.S + DESCRIPTION: + This file contains the assembly equivalent C code of drc process functions. + ========================================================================= */ + +#include "drc_lib.h" +#include "CDrcLib.h" +#include +#include "audio_basic_op_ext.h" +#include "audio_log10.h" +#include "audio_exp10.h" +#ifdef DRCLIB_OPT + +const static int16 shift_factor_arr[] = { 0, 0, -1, -15, -2, -15, -15, -15, -3 }; + +const static uint16 mult_factor_arr[] = { 1, 1, 1, ONE_BY_3_Q15, 1, ONE_BY_5_Q15, ONE_BY_6_Q15, ONE_BY_7_Q15, 1 }; + +DRC_RESULT ProcessBP16(drc_state_struct_t * pState, + drc_static_struct_t *pStatic, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr); +DRC_RESULT ProcessBP32(drc_state_struct_t * pState, + drc_static_struct_t *pStatic, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr); +DRC_RESULT ProcessMono16(drc_lib_t *pDrcLib, + uint16 negativeDrcTavUL16Q16, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr); +DRC_RESULT ProcessMono32(drc_lib_t *pDrcLib, + uint16 negativeDrcTavUL16Q16, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr); +DRC_RESULT ProcessMC16Linked(drc_lib_t *pDrcLib, + uint16 negativeDrcTavUL16Q16, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr); +DRC_RESULT ProcessMC16Unlinked(drc_lib_t *pDrcLib, + uint16 negativeDrcTavUL16Q16, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr); +DRC_RESULT ProcessMC32Linked(drc_lib_t *pDrcLib, + uint16 negativeDrcTavUL16Q16, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr); +DRC_RESULT ProcessMC32Unlinked(drc_lib_t *pDrcLib, + uint16 negativeDrcTavUL16Q16, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr); +static void compute_drc_gain(drc_state_struct_t *pState, drc_config_t *drc_config_ptr, uint32 gainNum); + +/*====================================================================== + +FUNCTION ProcessBP16 + +DESCRIPTION DRC processing for 16bit Bypass mode + +PARAMETERS pState: [in] pointer to the state structure + pStatic: [in] pointer to the config structure + nSamplePerChannel: [in] number of samples per channel + delayBuffSize: [in] size of delay buffer + pOutPtr: [out] pointer to the output data + pInPtr: [in] pointer to the input data + +RETURN VALUE Failure or Success + +SIDE EFFECTS None. + +======================================================================*/ +DRC_RESULT ProcessBP16(drc_state_struct_t * pState, + drc_static_struct_t *pStatic, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr) +{ + uint32 i, channelNo; + uint32 process_index; + uint32 input_index; + int16 *delayBuffer; + int16 *pInput, *pOutput; + + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + { + delayBuffer = (int16 *)pState->delayBuffer[channelNo]; + pInput = (int16 *)pInPtr[channelNo]; + pOutput = (int16 *)pOutPtr[channelNo]; + + process_index = pState->processIndex; + input_index = pState->inputIndex; + + for (i = 0; i < nSamplePerChannel; i++) + { + delayBuffer[input_index] = pInput[i]; + input_index++; + process_index = s32_modwrap_s32_u32(process_index, delayBuffSize); + + pOutput[i] = delayBuffer[process_index]; + process_index++; + input_index = s32_modwrap_s32_u32(input_index, delayBuffSize); + } + } + process_index = s32_modwrap_s32_u32(process_index, delayBuffSize); + pState->processIndex = process_index; + pState->inputIndex = input_index; + + return DRC_SUCCESS; +} + +/*====================================================================== + +FUNCTION ProcessBP32 + +DESCRIPTION DRC processing for 32bit Bypass mode + +PARAMETERS pState: [in] pointer to the state structure + pStatic: [in] pointer to the config structure + nSamplePerChannel: [in] number of samples per channel + delayBuffSize: [in] size of delay buffer + pOutPtr: [out] pointer to the output data + pInPtr: [in] pointer to the input data + +RETURN VALUE Failure or Success + +SIDE EFFECTS None. + +======================================================================*/ + +DRC_RESULT ProcessBP32(drc_state_struct_t * pState, + drc_static_struct_t *pStatic, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr) +{ + uint32 i, channelNo; + uint32 process_index; + uint32 input_index; + int32 *delayBuffer; + int32 *pInput, *pOutput; + + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + { + delayBuffer = (int32 *)pState->delayBuffer[channelNo]; + pInput = (int32 *)pInPtr[channelNo]; + pOutput = (int32 *)pOutPtr[channelNo]; + + process_index = pState->processIndex; + input_index = pState->inputIndex; + + for (i = 0; i < nSamplePerChannel; i++) + { + delayBuffer[input_index] = pInput[i]; + input_index++; + process_index = s32_modwrap_s32_u32(process_index, delayBuffSize); + + pOutput[i] = delayBuffer[process_index]; + process_index++; + input_index = s32_modwrap_s32_u32(input_index, delayBuffSize); + } + } + process_index = s32_modwrap_s32_u32(process_index, delayBuffSize); + pState->processIndex = process_index; + pState->inputIndex = input_index; + + return DRC_SUCCESS; +} + +/*====================================================================== + +FUNCTION ProcessMono16 + +DESCRIPTION DRC processing for 16bit single channel case + +PARAMETERS pDrcLib: [in] pointer to the library structure + negativeDrcTavUL16Q16: [in] precalculated negative time constant + nSamplePerChannel: [in] number of samples per channel + delayBuffSize: [in] size of delay buffer + pOutPtr: [out] pointer to the output data + pInPtr: [in] pointer to the input data + +RETURN VALUE Failure or Success + +SIDE EFFECTS None. + +======================================================================*/ + +DRC_RESULT ProcessMono16(drc_lib_t *pDrcLib, + uint16 negativeDrcTavUL16Q16, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr) +{ + uint32 i; + int16 *output; + int16 processData; + int16 *delayBuffer; + int32 tempL32; + int64 tempOutL64; + int32 currDrcRmsL32; + + drc_lib_mem_t * pDrcLibMem = (drc_lib_mem_t *)pDrcLib->lib_mem_ptr; + drc_state_struct_t *pState = pDrcLibMem->drc_state_struct_ptr; + drc_config_t * drc_config_ptr = pDrcLibMem->drc_config_ptr; + + delayBuffer = (int16 *)pState->delayBuffer[0]; + output = (int16 *)(pOutPtr[0]); + + for (i = 0; i < nSamplePerChannel; i++) + { + delayBuffer[pState->inputIndex] = ((int16 *)pInPtr[0])[i]; + + //------------ Compute long term RMS of DRC for mono case: ------------ + + currDrcRmsL32 = s32_mult_s16_s16(delayBuffer[pState->inputIndex], delayBuffer[pState->inputIndex]); + + // current_drcRms = previous_drcRms*(1-drcTav) + drcTav * x[n] ^ 2 + pState->rmsStateL32[0] = + s32_extract_s64_l(s64_add_s64_s64(s64_mult_s32_u16_shift(currDrcRmsL32, drc_config_ptr->rmsTavUL16Q16, 0), + s64_mult_s32_u16_shift(pState->rmsStateL32[0], negativeDrcTavUL16Q16, 0))); + + //---------------- Update gain computation only if at downSampleLevels -------- + if (0 == pState->downSampleCounter) + { + pState->downSampleCounter = drc_config_ptr->downSampleLevel; + // compute the current INPUT RMS in dB (log domain) + // log10_fixed: input L32Q0 format, output Q23 format + if (pState->rmsStateL32[0] != 0) + { + pState->drcRmsDBL32Q23[0] = + log10_fixed(pState->rmsStateL32[0]); // log10_fixed is 10*log10(.) in fixed point + } + else + { + pState->drcRmsDBL32Q23[0] = MIN_RMS_DB_L32Q23; + } + + // ----------- Compute DRC gain ------------------------ + compute_drc_gain(pState, drc_config_ptr, 0); + } + pState->downSampleCounter--; + + // ----------- Apply DRC gain -------------------------- + // delayBuffer = (int16 *) (pState->delayBuffer[0]); + processData = delayBuffer[pState->processIndex]; + tempL32 = s32_saturate_s64(s64_mult_s32_s16_shift(pState->gainUL32Q15[0], + processData, + 1)); // apply gain and output has same QFactor as input + + if (drc_config_ptr->makeupGainUL16Q12 != MAKEUPGAIN_UNITY) // Implement only non-unity gain + { + // Multiply output with the shift normalized makeup gain + tempOutL64 = s64_mult_s32_u16_shift(tempL32, drc_config_ptr->makeupGainUL16Q12, 4); + tempL32 = s32_saturate_s64(tempOutL64); + } + + // output results + output[i] = s16_saturate_s32(tempL32); + + // Check if Delay buffer reaches the cirulary bundary + pState->processIndex++; + pState->inputIndex++; + + pState->processIndex = s32_modwrap_s32_u32(pState->processIndex, delayBuffSize); + pState->inputIndex = s32_modwrap_s32_u32(pState->inputIndex, delayBuffSize); + } + + return DRC_SUCCESS; +} + +/*====================================================================== + +FUNCTION ProcessMono32 + +DESCRIPTION DRC processing for 32bit single channel case + +PARAMETERS pDrcLib: [in] pointer to the library structure + negativeDrcTavUL16Q16: [in] precalculated negative time constant + nSamplePerChannel: [in] number of samples per channel + delayBuffSize: [in] size of delay buffer + pOutPtr: [out] pointer to the output data + pInPtr: [in] pointer to the input data + +RETURN VALUE Failure or Success + +SIDE EFFECTS None. + +======================================================================*/ + +DRC_RESULT ProcessMono32(drc_lib_t *pDrcLib, + uint16 negativeDrcTavUL16Q16, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr) +{ + uint32 i; + int32 *output; + int32 processData; + int32 *delayBuffer; + int32 tempL32; + int64 currDrcRmsL64, tempOutL64; + int32 currDrcRmsL32; + + drc_lib_mem_t * pDrcLibMem = (drc_lib_mem_t *)pDrcLib->lib_mem_ptr; + drc_state_struct_t *pState = pDrcLibMem->drc_state_struct_ptr; + drc_config_t * drc_config_ptr = pDrcLibMem->drc_config_ptr; + + delayBuffer = (int32 *)pState->delayBuffer[0]; + output = (int32 *)(pOutPtr[0]); + + for (i = 0; i < nSamplePerChannel; i++) + { + + delayBuffer[pState->inputIndex] = ((int32 *)pInPtr[0])[i]; + //-------- Compute long term input RMS for DRC --------- + + // x[n] ^ 2 + currDrcRmsL64 = s64_mult_s32_s32(delayBuffer[pState->inputIndex], delayBuffer[pState->inputIndex]); + // Right shift to get to the same Q-factor as in the 16-bits data width case. + currDrcRmsL32 = s32_saturate_s64(s64_shl_s64(currDrcRmsL64, s16_shl_s16(s16_sub_s16_s16(Q15, Q27), ONE))); + + // current_drcRms = previous_drcRms*(1-drcTav) + drcTav * x[n] ^ 2 + pState->rmsStateL32[0] = + s32_extract_s64_l(s64_add_s64_s64(s64_mult_s32_u16_shift(currDrcRmsL32, drc_config_ptr->rmsTavUL16Q16, 0), + s64_mult_s32_u16_shift(pState->rmsStateL32[0], negativeDrcTavUL16Q16, 0))); + + //---------------- Update gain computation only if at downSampleLevels -------- + if (0 == pState->downSampleCounter) + { + pState->downSampleCounter = drc_config_ptr->downSampleLevel; + // compute the current INPUT RMS in dB (log domain) + // log10_fixed: input L32Q0 format, output Q23 format + if (pState->rmsStateL32[0] != 0) + { + pState->drcRmsDBL32Q23[0] = + log10_fixed(pState->rmsStateL32[0]); // log10_fixed is 10*log10(.) in fixed point + } + else + { + pState->drcRmsDBL32Q23[0] = MIN_RMS_DB_L32Q23; + } + + // ----------- Compute DRC gain ------------------------ + compute_drc_gain(pState, drc_config_ptr, 0); + } + pState->downSampleCounter--; + + // ---------- Apply DRC gain ---------------------- + processData = delayBuffer[pState->processIndex]; + + tempOutL64 = s64_mult_s32_s32_shift(processData, + pState->gainUL32Q15[0], + 17); // apply gain and output has same QFactor as input + tempL32 = s32_saturate_s64(tempOutL64); + + // apply make up gain if needed + if (drc_config_ptr->makeupGainUL16Q12 != MAKEUPGAIN_UNITY) // Implement only non-unity gain + { + // Multiply output with the shift normalized makeup gain + tempOutL64 = s64_mult_s32_u16_shift(tempL32, drc_config_ptr->makeupGainUL16Q12, 4); + tempL32 = s32_saturate_s64(tempOutL64); + } + + // output results + + output[i] = tempL32; + + // Check if Delay buffer reaches the cirulary bundary + pState->processIndex++; + pState->inputIndex++; + + pState->processIndex = s32_modwrap_s32_u32(pState->processIndex, delayBuffSize); + pState->inputIndex = s32_modwrap_s32_u32(pState->inputIndex, delayBuffSize); + } + + return DRC_SUCCESS; +} + +/*====================================================================== + +FUNCTION ProcessMC16Linked + +DESCRIPTION DRC processing for 16bit multichannel linked case + +PARAMETERS pDrcLib: [in] pointer to the library structure + negativeDrcTavUL16Q16: [in] precalculated negative time constant + nSamplePerChannel: [in] number of samples per channel + delayBuffSize: [in] size of delay buffer + pOutPtr: [out] pointer to the output data + pInPtr: [in] pointer to the input data + +RETURN VALUE Failure or Success + +SIDE EFFECTS None. + +======================================================================*/ + +DRC_RESULT ProcessMC16Linked(drc_lib_t *pDrcLib, + uint16 negativeDrcTavUL16Q16, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr) +{ + + uint32 i, channelNo; + int16 *output; + int16 processData; + int16 *delayBuffer; + int32 tempL32; + int64 tempOutL64; + int32 currDrcRmsL32; + int64 squareInputL64; + + drc_lib_mem_t * pDrcLibMem = (drc_lib_mem_t *)pDrcLib->lib_mem_ptr; + drc_state_struct_t * pState = pDrcLibMem->drc_state_struct_ptr; + drc_static_struct_t *pStatic = pDrcLibMem->drc_static_struct_ptr; + drc_config_t * drc_config_ptr = pDrcLibMem->drc_config_ptr; + + const int16 shift_factor = shift_factor_arr[pStatic->num_channel]; + const uint16 mult_factor = mult_factor_arr[pStatic->num_channel]; + + for (i = 0; i < nSamplePerChannel; i++) + { + squareInputL64 = 0; + + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + { + delayBuffer = (int16 *)pState->delayBuffer[channelNo]; + delayBuffer[pState->inputIndex] = ((int16 *)pInPtr[channelNo])[i]; + //-------- Compute long term input RMS for DRC --------- + squareInputL64 = s64_add_s64_s64(squareInputL64, + s64_mult_s16_s16_shift(delayBuffer[pState->inputIndex], + delayBuffer[pState->inputIndex], + 0)); + } + + // calculate squareInputL64 = squareInputL64/pDrcLib->drc_static_struct.num_channel + squareInputL64 = s64_shl_s64(squareInputL64, shift_factor); + if (mult_factor != 1) + { + squareInputL64 = s64_mult_s32_s16(s32_saturate_s64(squareInputL64), mult_factor); + } + + currDrcRmsL32 = s32_saturate_s64(squareInputL64); + + // current_drcRms = previous_drcRms*(1-drcTav) + drcTav * x[n] ^ 2 + pState->rmsStateL32[0] = + s32_extract_s64_l(s64_add_s64_s64(s64_mult_s32_u16_shift(currDrcRmsL32, drc_config_ptr->rmsTavUL16Q16, 0), + s64_mult_s32_u16_shift(pState->rmsStateL32[0], negativeDrcTavUL16Q16, 0))); + + //---------------- Update gain computation only if at downSampleLevels -------- + if (0 == pState->downSampleCounter) + { + pState->downSampleCounter = drc_config_ptr->downSampleLevel; + // compute the current INPUT RMS in dB (log domain) + // log10_fixed: input L32Q0 format, output Q23 format + if (pState->rmsStateL32[0] != 0) + { + pState->drcRmsDBL32Q23[0] = + log10_fixed(pState->rmsStateL32[0]); // log10_fixed is 10*log10(.) in fixed point + } + else + { + pState->drcRmsDBL32Q23[0] = MIN_RMS_DB_L32Q23; + } + + // ----------- Compute DRC gain ------------------------ + compute_drc_gain(pState, drc_config_ptr, 0); + } + pState->downSampleCounter--; + + // ---------- Apply DRC gain ---------------------- + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + { + delayBuffer = (int16 *)(pState->delayBuffer[channelNo]); + output = (int16 *)(pOutPtr[channelNo]); + processData = delayBuffer[pState->processIndex]; + + tempL32 = s32_saturate_s64(s64_mult_s32_s16_shift(pState->gainUL32Q15[0], + processData, + 1)); // apply gain and output has same QFactor as input + + // apply make up gain if needed + if (drc_config_ptr->makeupGainUL16Q12 != MAKEUPGAIN_UNITY) // Implement only non-unity gain + { + // Multiply output with the shift normalized makeup gain + tempOutL64 = s64_mult_s32_u16_shift(tempL32, drc_config_ptr->makeupGainUL16Q12, 4); + tempL32 = s32_saturate_s64(tempOutL64); + } + output[i] = s16_saturate_s32(tempL32); + } + + // Check if Delay buffer reaches the cirulary bundary + pState->processIndex++; + pState->inputIndex++; + + pState->processIndex = s32_modwrap_s32_u32(pState->processIndex, delayBuffSize); + pState->inputIndex = s32_modwrap_s32_u32(pState->inputIndex, delayBuffSize); + } + + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + { + pState->currState[channelNo] = pState->currState[0]; // use same currState for all channels + pState->gainUL32Q15[channelNo] = pState->gainUL32Q15[0]; // use same gain for all channels + } + + return DRC_SUCCESS; +} + +/*====================================================================== + +FUNCTION ProcessMC16Unlinked + +DESCRIPTION DRC processing for 16bit multichannel unlinked case + +PARAMETERS pDrcLib: [in] pointer to the library structure + negativeDrcTavUL16Q16: [in] precalculated negative time constant + nSamplePerChannel: [in] number of samples per channel + delayBuffSize: [in] size of delay buffer + pOutPtr: [out] pointer to the output data + pInPtr: [in] pointer to the input data + +RETURN VALUE Failure or Success + +SIDE EFFECTS None. + +======================================================================*/ + +DRC_RESULT ProcessMC16Unlinked(drc_lib_t *pDrcLib, + uint16 negativeDrcTavUL16Q16, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr) +{ + + uint32 i, channelNo; + int16 *output; + int16 processData; + int16 *delayBuffer; + int32 tempL32; + int64 tempOutL64; + int32 currDrcRmsL32; + + drc_lib_mem_t * pDrcLibMem = (drc_lib_mem_t *)pDrcLib->lib_mem_ptr; + drc_state_struct_t * pState = pDrcLibMem->drc_state_struct_ptr; + drc_static_struct_t *pStatic = pDrcLibMem->drc_static_struct_ptr; + drc_config_t * drc_config_ptr = pDrcLibMem->drc_config_ptr; + + int16 downsample_counter; + int16 process_index; + int16 input_index; + + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + { + delayBuffer = (int16 *)pState->delayBuffer[channelNo]; + output = (int16 *)(pOutPtr[channelNo]); + + downsample_counter = pState->downSampleCounter; + process_index = pState->processIndex; + input_index = pState->inputIndex; + + for (i = 0; i < nSamplePerChannel; i++) + { + delayBuffer[input_index] = ((int16 *)pInPtr[channelNo])[i]; + + //-------- Compute long term input RMS for DRC --------- + currDrcRmsL32 = s32_mult_s16_s16(delayBuffer[input_index], delayBuffer[input_index]); + + pState->rmsStateL32[channelNo] = s32_extract_s64_l( + s64_add_s64_s64(s64_mult_s32_u16_shift(currDrcRmsL32, drc_config_ptr->rmsTavUL16Q16, 0), + s64_mult_s32_u16_shift(pState->rmsStateL32[channelNo], negativeDrcTavUL16Q16, 0))); + + //---------------- Update gain computation only if at downSampleLevels -------- + if (0 == downsample_counter) + { + downsample_counter = drc_config_ptr->downSampleLevel; + // compute the current INPUT RMS in dB (log domain) + // log10_fixed: input L32Q0 format, output Q23 format + if (pState->rmsStateL32[channelNo] != 0) + { + pState->drcRmsDBL32Q23[channelNo] = + log10_fixed(pState->rmsStateL32[channelNo]); // log10_fixed is 10*log10(.) in fixed point + } + else + { + pState->drcRmsDBL32Q23[channelNo] = MIN_RMS_DB_L32Q23; + } + + // ----------- Compute DRC gain ------------------------ + compute_drc_gain(pState, drc_config_ptr, channelNo); + } + downsample_counter--; + + // ---------- Apply DRC gain ---------------------- + processData = delayBuffer[process_index]; + tempL32 = s32_saturate_s64(s64_mult_s32_s16_shift(pState->gainUL32Q15[channelNo], + processData, + 1)); // apply gain and output has same QFactor as input + + // apply make up gain if needed + if (drc_config_ptr->makeupGainUL16Q12 != MAKEUPGAIN_UNITY) // Implement only non-unity gain + { + // Multiply output with the shift normalized makeup gain + tempOutL64 = s64_mult_s32_u16_shift(tempL32, drc_config_ptr->makeupGainUL16Q12, 4); + tempL32 = s32_saturate_s64(tempOutL64); + } + + // output results + output[i] = s16_saturate_s32(tempL32); + + // Check if Delay buffer reaches the cirulary bundary + process_index++; + input_index++; + + process_index = s32_modwrap_s32_u32(process_index, delayBuffSize); + input_index = s32_modwrap_s32_u32(input_index, delayBuffSize); + } + } + + pState->downSampleCounter = downsample_counter; + pState->processIndex = process_index; + pState->inputIndex = input_index; + + return DRC_SUCCESS; +} + +/*====================================================================== + +FUNCTION ProcessMC32Linked + +DESCRIPTION DRC processing for 32bit multichannel linked case + +PARAMETERS pDrcLib: [in] pointer to the library structure + negativeDrcTavUL16Q16: [in] precalculated negative time constant + nSamplePerChannel: [in] number of samples per channel + delayBuffSize: [in] size of delay buffer + pOutPtr: [out] pointer to the output data + pInPtr: [in] pointer to the input data + +RETURN VALUE Failure or Success + +SIDE EFFECTS None. + +======================================================================*/ + +DRC_RESULT ProcessMC32Linked(drc_lib_t *pDrcLib, + uint16 negativeDrcTavUL16Q16, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr) +{ + uint32 i, channelNo; + int32 *output; + int32 processData; + int32 *delayBuffer; + int32 tempL32; + int64 tempOutL64; + int64 squareInputL64; + int64 currDrcRmsL64; + int32 currDrcRmsL32; + + drc_lib_mem_t * pDrcLibMem = (drc_lib_mem_t *)pDrcLib->lib_mem_ptr; + drc_state_struct_t * pState = pDrcLibMem->drc_state_struct_ptr; + drc_static_struct_t *pStatic = pDrcLibMem->drc_static_struct_ptr; + drc_config_t * drc_config_ptr = pDrcLibMem->drc_config_ptr; + + const int16 shift_factor = shift_factor_arr[pStatic->num_channel]; + const uint16 mult_factor = mult_factor_arr[pStatic->num_channel]; + + for (i = 0; i < nSamplePerChannel; i++) + { + squareInputL64 = 0; + + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + { + delayBuffer = (int32 *)pState->delayBuffer[channelNo]; + delayBuffer[pState->inputIndex] = ((int32 *)pInPtr[channelNo])[i]; + + //-------- Compute long term input RMS for DRC --------- + // For stereo linked, currDrcRms = (xL(n)^2 + xR(n)^2)/2 (stereo channel example) + currDrcRmsL64 = s64_mult_s32_s32(delayBuffer[pState->inputIndex], delayBuffer[pState->inputIndex]); + currDrcRmsL64 = s64_shl_s64(currDrcRmsL64, s16_shl_s16(s16_sub_s16_s16(Q15, Q27), ONE)); + squareInputL64 = s64_add_s64_s64(squareInputL64, currDrcRmsL64); + } + + // calculate squareInputL64 = squareInputL64/pDrcLib->drc_static_struct.num_channel + squareInputL64 = s64_shl_s64(squareInputL64, shift_factor); + if (mult_factor != 1) + { + squareInputL64 = s64_mult_s32_s16(s32_saturate_s64(squareInputL64), mult_factor); + } + + currDrcRmsL32 = s32_saturate_s64(squareInputL64); + + // current_drcRms = previous_drcRms*(1-drcTav) + drcTav * x[n] ^ 2 + pState->rmsStateL32[0] = + s32_extract_s64_l(s64_add_s64_s64(s64_mult_s32_u16_shift(currDrcRmsL32, drc_config_ptr->rmsTavUL16Q16, 0), + s64_mult_s32_u16_shift(pState->rmsStateL32[0], negativeDrcTavUL16Q16, 0))); + + //---------------- Update gain computation only if at downSampleLevels -------- + if (0 == pState->downSampleCounter) + { + pState->downSampleCounter = drc_config_ptr->downSampleLevel; + // compute the current INPUT RMS in dB (log domain) + // log10_fixed: input L32Q0 format, output Q23 format + if (pState->rmsStateL32[0] != 0) + { + pState->drcRmsDBL32Q23[0] = + log10_fixed(pState->rmsStateL32[0]); // log10_fixed is 10*log10(.) in fixed point + } + else + { + pState->drcRmsDBL32Q23[0] = MIN_RMS_DB_L32Q23; + } + + // ----------- Compute DRC gain ------------------------ + compute_drc_gain(pState, drc_config_ptr, 0); + } + pState->downSampleCounter--; + + // ---------- Apply DRC gain ---------------------- + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + { + delayBuffer = (int32 *)(pState->delayBuffer[channelNo]); + output = (int32 *)(pOutPtr[channelNo]); + processData = delayBuffer[pState->processIndex]; + + tempOutL64 = s64_mult_s32_s32_shift(processData, + pState->gainUL32Q15[0], + 17); // apply gain and output has same QFactor as input + tempL32 = s32_saturate_s64(tempOutL64); + + // apply make up gain if needed + if (drc_config_ptr->makeupGainUL16Q12 != MAKEUPGAIN_UNITY) // Implement only non-unity gain + { + // Multiply output with the shift normalized makeup gain + tempOutL64 = s64_mult_s32_u16_shift(tempL32, drc_config_ptr->makeupGainUL16Q12, 4); + tempL32 = s32_saturate_s64(tempOutL64); + } + + output[i] = tempL32; + } + + // Check if Delay buffer reaches the cirulary bundary + pState->processIndex++; + pState->inputIndex++; + + pState->processIndex = s32_modwrap_s32_u32(pState->processIndex, delayBuffSize); + pState->inputIndex = s32_modwrap_s32_u32(pState->inputIndex, delayBuffSize); + } + + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + { + pState->currState[channelNo] = pState->currState[0]; // use same currState for all channels + pState->gainUL32Q15[channelNo] = pState->gainUL32Q15[0]; // use same gain for all channels + } + + return DRC_SUCCESS; +} + +/*====================================================================== + +FUNCTION ProcessMC32Unlinked + +DESCRIPTION DRC processing for 32bit multichannel unlinked case + +PARAMETERS pDrcLib: [in] pointer to the library structure + negativeDrcTavUL16Q16: [in] precalculated negative time constant + nSamplePerChannel: [in] number of samples per channel + delayBuffSize: [in] size of delay buffer + pOutPtr: [out] pointer to the output data + pInPtr: [in] pointer to the input data + +RETURN VALUE Failure or Success + +SIDE EFFECTS None. + +======================================================================*/ + +DRC_RESULT ProcessMC32Unlinked(drc_lib_t *pDrcLib, + uint16 negativeDrcTavUL16Q16, + uint32 nSamplePerChannel, + uint32 delayBuffSize, + int8 ** pOutPtr, + int8 ** pInPtr) +{ + uint32 i, channelNo; + int32 *output; + int32 processData; + int32 *delayBuffer; + int32 tempL32; + int64 tempOutL64; + int64 currDrcRmsL64; + int32 currDrcRmsL32; + + drc_lib_mem_t * pDrcLibMem = (drc_lib_mem_t *)pDrcLib->lib_mem_ptr; + drc_state_struct_t * pState = pDrcLibMem->drc_state_struct_ptr; + drc_static_struct_t *pStatic = pDrcLibMem->drc_static_struct_ptr; + drc_config_t * drc_config_ptr = pDrcLibMem->drc_config_ptr; + + int16 downsample_counter; + int16 process_index; + int16 input_index; + + for (channelNo = 0; channelNo < pStatic->num_channel; channelNo++) + { + delayBuffer = (int32 *)pState->delayBuffer[channelNo]; + output = (int32 *)(pOutPtr[channelNo]); + + downsample_counter = pState->downSampleCounter; + process_index = pState->processIndex; + input_index = pState->inputIndex; + + for (i = 0; i < nSamplePerChannel; i++) + { + delayBuffer[input_index] = ((int32 *)pInPtr[channelNo])[i]; + + // -------- Compute long term input RMS for DRC --------- + // x[n] ^ 2 for each channelNo separately & is same Q-factor as in the 16-bit width case. + currDrcRmsL64 = s64_mult_s32_s32(delayBuffer[input_index], delayBuffer[input_index]); + currDrcRmsL64 = s64_shl_s64(currDrcRmsL64, s16_shl_s16(s16_sub_s16_s16(Q15, Q27), ONE)); + currDrcRmsL32 = s32_saturate_s64(currDrcRmsL64); + + pState->rmsStateL32[channelNo] = s32_extract_s64_l( + s64_add_s64_s64(s64_mult_s32_u16_shift(currDrcRmsL32, drc_config_ptr->rmsTavUL16Q16, 0), + s64_mult_s32_u16_shift(pState->rmsStateL32[channelNo], negativeDrcTavUL16Q16, 0))); + + //---------------- Update gain computation only if at downSampleLevels -------- + if (0 == downsample_counter) + { + downsample_counter = drc_config_ptr->downSampleLevel; + // compute the current INPUT RMS in dB (log domain) + // log10_fixed: input L32Q0 format, output Q23 format + if (pState->rmsStateL32[channelNo] != 0) + { + pState->drcRmsDBL32Q23[channelNo] = + log10_fixed(pState->rmsStateL32[channelNo]); // log10_fixed is 10*log10(.) in fixed point + } + else + { + pState->drcRmsDBL32Q23[channelNo] = MIN_RMS_DB_L32Q23; + } + + // ----------- Compute DRC gain ------------------------ + compute_drc_gain(pState, drc_config_ptr, channelNo); + } + downsample_counter--; + + // ---------- Apply DRC gain ---------------------- + processData = delayBuffer[process_index]; + + tempOutL64 = s64_mult_s32_s32_shift(processData, + pState->gainUL32Q15[channelNo], + 17); // apply gain and output has same QFactor as input + tempL32 = s32_saturate_s64(tempOutL64); + + // apply make up gain if needed + if (drc_config_ptr->makeupGainUL16Q12 != MAKEUPGAIN_UNITY) // Implement only non-unity gain + { + // Multiply output with the shift normalized makeup gain + tempOutL64 = s64_mult_s32_u16_shift(tempL32, drc_config_ptr->makeupGainUL16Q12, 4); + tempL32 = s32_saturate_s64(tempOutL64); + } + // output results + output[i] = tempL32; + + // Check if Delay buffer reaches the cirulary bundary + process_index++; + input_index++; + + process_index = s32_modwrap_s32_u32(process_index, delayBuffSize); + input_index = s32_modwrap_s32_u32(input_index, delayBuffSize); + } + } + + pState->downSampleCounter = downsample_counter; + pState->processIndex = process_index; + pState->inputIndex = input_index; + + return DRC_SUCCESS; +} + +/*====================================================================== + +FUNCTION compute_drc_gain + +DESCRIPTION Computes the DRC gain for current input samples + +PARAMETERS pState: [in/out] pointer to the state structure that saves the DRC processing states +drc_config_ptr:[in] Pointer to the calibration structure + +RETURN VALUE Computed drc gain value for current input samples + +SIDE EFFECTS None. + +======================================================================*/ +void compute_drc_gain(drc_state_struct_t *pState, drc_config_t *drc_config_ptr, uint32 j) +{ + + // ------------ Variable Declaration ------------ + int16 drcRmsDBL16Q7, outDrcRmsDBL16Q7; + int32 drcRmsDBL32Q23, outDrcRmsDBL32Q23; + uint32 newTargetGainUL32Q15; + int64 currDrcGainL64Q27, gainDiffL64Q27; + DrcStateType currState; + int32 tempOutL32; + uint16 tempSlopeUL16Q16; + int16 tempThresholdL16Q7, tempSlopeL16Q8; + int32 newTargetGainL32Q26, newTargetGainL32Q23; + int64 tempRmsDBL40Q23; + + //---------------- Update gain computation only if at downSampleLevels -------- + + currState = pState->currState[j]; + + drcRmsDBL32Q23 = pState->drcRmsDBL32Q23[j]; // current INPUT RMS in dB (log domain) + drcRmsDBL16Q7 = s16_extract_s32_h(drcRmsDBL32Q23); // Q7 + drcRmsDBL16Q7 = s16_max_s16_s16(drcRmsDBL16Q7, MIN_RMS_DB_L16Q7); + + // compute the current output RMS in dB (log domain) + // outDrcRmsDB = 10*log10(input*gainUL32Q15)^2 + // convert UL32 to L32 format that log10_fixed can take as input + tempOutL32 = s32_saturate_s64(s64_shl_s64(pState->gainUL32Q15[j], -1)); + // log10_fixed: input L32Q0 format, output Q23 format + if (tempOutL32 != 0) + { + outDrcRmsDBL32Q23 = log10_fixed(tempOutL32); + outDrcRmsDBL32Q23 = + (int32)s64_add_s32_s32((int32)s64_sub_s64_s64(s64_shl_s64(outDrcRmsDBL32Q23, 1), DB_16384_L32Q23), + drcRmsDBL32Q23); + } + else + { + outDrcRmsDBL32Q23 = MIN_RMS_DB_L32Q23; + } + outDrcRmsDBL16Q7 = s16_extract_s32_h(outDrcRmsDBL32Q23); + + // ---- Compute the Target Gain for the current sample based on input RMS ---- + // newTargetGainUL32Q15 = target_gain_comp(drc_config_ptr, drcRmsDBL16Q7); + + // figure out what part of compression curve the rms value is in + if (drcRmsDBL16Q7 > drc_config_ptr->dnCompThresholdL16Q7) + // in Down Dompression + { + tempThresholdL16Q7 = drc_config_ptr->dnCompThresholdL16Q7; + tempSlopeUL16Q16 = drc_config_ptr->dnCompSlopeUL16Q16; + + // newTarget = (dwCompThreshold - Xrms[n]) * dwCompSlopeUL16Q16 + newTargetGainL32Q23 = s32_mult_s16_u16(s16_sub_s16_s16(tempThresholdL16Q7, drcRmsDBL16Q7), + tempSlopeUL16Q16); // Q23 + } + else if (drcRmsDBL16Q7 < drc_config_ptr->dnExpaThresholdL16Q7) + // in Down Expansion + { + tempThresholdL16Q7 = drc_config_ptr->upCompThresholdL16Q7; + tempSlopeUL16Q16 = drc_config_ptr->upCompSlopeUL16Q16; + + // newTarget = (dwExpaThresholdL16Q7 - Xrms[n])*dwExpaSlopeUL16Q16 + ... + // (uwCompThresholdL16Q7 - dwExpaThresholdL16Q7)*uwCompSlopeUL16Q16; + newTargetGainL32Q23 = s32_mult_s16_u16(s16_sub_s16_s16(tempThresholdL16Q7, drc_config_ptr->dnExpaThresholdL16Q7), + tempSlopeUL16Q16); // Q23 + + tempThresholdL16Q7 = drc_config_ptr->dnExpaThresholdL16Q7; + tempSlopeL16Q8 = drc_config_ptr->dnExpaSlopeL16Q8; + + tempRmsDBL40Q23 = s64_mult_s16_s16_shift(s16_sub_s16_s16(tempThresholdL16Q7, drcRmsDBL16Q7), tempSlopeL16Q8, 8); + newTargetGainL32Q23 = s32_saturate_s64(s64_add_s64_s32(tempRmsDBL40Q23, newTargetGainL32Q23)); + // L32Q23 + + // Limit the gain reduction in Downward Expander part to be dnExpaMinGainDB + if (newTargetGainL32Q23 < drc_config_ptr->dnExpaMinGainDBL32Q23) + { + newTargetGainL32Q23 = drc_config_ptr->dnExpaMinGainDBL32Q23; + } + } + else if (drcRmsDBL16Q7 < drc_config_ptr->upCompThresholdL16Q7) + // in Up Dompression + { + tempThresholdL16Q7 = drc_config_ptr->upCompThresholdL16Q7; + tempSlopeUL16Q16 = drc_config_ptr->upCompSlopeUL16Q16; + + // newTarget = (uwCompThreshold - Xrms[n]) * uwCompSlopeUL16Q16 + newTargetGainL32Q23 = s32_mult_s16_u16(s16_sub_s16_s16(tempThresholdL16Q7, drcRmsDBL16Q7), + tempSlopeUL16Q16); // Q23 + } + else + { + newTargetGainL32Q23 = 0x00000000; + } + + // calculate new target gain = 10^(new target gain log / 20): input L32Q26, out:L32Q15 + newTargetGainL32Q26 = (int32)s64_mult_s32_s16_shift(newTargetGainL32Q23, ONE_OVER_TWENTY_UQ19, 0); + newTargetGainUL32Q15 = exp10_fixed(newTargetGainL32Q26); // Q15 + pState->targetGainUL32Q15[j] = newTargetGainUL32Q15; + + // --- Find out the appropriate time constant based on output RMS --- + if (outDrcRmsDBL16Q7 > pState->outDnCompThresholdL16Q7) + { + if (newTargetGainUL32Q15 < pState->gainUL32Q15[j]) + { + pState->timeConstantUL32Q31[j] = drc_config_ptr->dnCompAttackUL32Q31; + currState = ATTACK; + } + else if (newTargetGainUL32Q15 >= + (uint32)s32_saturate_s64( + s64_mult_u32_s16_shift(pState->gainUL32Q15[j], drc_config_ptr->dnCompHysterisisUL16Q14, 2))) + { + pState->timeConstantUL32Q31[j] = drc_config_ptr->dnCompReleaseUL32Q31; + currState = RELEASE; + } + else + { + if (currState == ATTACK) + { + pState->timeConstantUL32Q31[j] = 0; + currState = NO_CHANGE; + } + } + + pState->dwcomp_state_change[j] = 0; + } + else if (outDrcRmsDBL16Q7 < pState->outDnExpaThresholdL16Q7) + { + if ((uint32)s32_saturate_s64( + s64_mult_u32_s16_shift(newTargetGainUL32Q15, drc_config_ptr->dnExpaHysterisisUL16Q14, 2)) < + pState->gainUL32Q15[j]) + { + pState->timeConstantUL32Q31[j] = drc_config_ptr->dnExpaAttackUL32Q31; + currState = ATTACK; + } + else if (newTargetGainUL32Q15 > pState->gainUL32Q15[j]) + { + pState->timeConstantUL32Q31[j] = drc_config_ptr->dnExpaReleaseUL32Q31; + currState = RELEASE; + } + else + { + if (currState == RELEASE) + { + pState->timeConstantUL32Q31[j] = 0; + currState = NO_CHANGE; + } + } + } + else if (outDrcRmsDBL16Q7 < pState->outUpCompThresholdL16Q7) + { + if (newTargetGainUL32Q15 < pState->gainUL32Q15[j]) + { + pState->timeConstantUL32Q31[j] = drc_config_ptr->upCompAttackUL32Q31; + currState = ATTACK; + } + else if (newTargetGainUL32Q15 >= + (uint32)s32_saturate_s64( + s64_mult_u32_s16_shift(pState->gainUL32Q15[j], drc_config_ptr->upCompHysterisisUL16Q14, 2))) + { + pState->timeConstantUL32Q31[j] = drc_config_ptr->upCompReleaseUL32Q31; + currState = RELEASE; + } + else + { + if (currState == ATTACK) + { + pState->timeConstantUL32Q31[j] = 0; + currState = NO_CHANGE; + } + } + + pState->uwcomp_state_change[j] = 0; + } + else + { + if (drcRmsDBL16Q7 > drc_config_ptr->dnCompThresholdL16Q7) + { + pState->timeConstantUL32Q31[j] = drc_config_ptr->dnCompAttackUL32Q31; + currState = ATTACK; + } + else if (drcRmsDBL16Q7 < drc_config_ptr->dnExpaThresholdL16Q7) + { + pState->timeConstantUL32Q31[j] = drc_config_ptr->dnExpaAttackUL32Q31; + currState = ATTACK; + } + else if (drcRmsDBL16Q7 < drc_config_ptr->upCompThresholdL16Q7) + { + pState->timeConstantUL32Q31[j] = drc_config_ptr->upCompReleaseUL32Q31; + currState = RELEASE; + } + else + { + // pState->timeConstantUL32Q31[j] = 0; + currState = NO_CHANGE; + + if (pState->dwcomp_state_change[j] == 0) + { + pState->timeConstantUL32Q31[j] = drc_config_ptr->dnCompReleaseUL32Q31; + currState = RELEASE; + pState->dwcomp_state_change[j] = 1; + } + else if (pState->uwcomp_state_change[j] == 0) + { + pState->timeConstantUL32Q31[j] = drc_config_ptr->upCompAttackUL32Q31; + currState = ATTACK; + pState->uwcomp_state_change[j] = 1; + } + } + } + + // --- calculate DRC gain with determined smooth factor --- + // drcGain = drcGain*(1-timeConstant) + drcTargetGain*timeConstant + // = drcGain + (drcTargetGain - drcGain)*timeConstant + gainDiffL64Q27 = s64_sub_s64_s64(((int64)pState->targetGainUL32Q15[j]) << 12, pState->instGainUL64Q27[j]); + currDrcGainL64Q27 = + s64_mult_s32_s32_shift(s32_saturate_s64(gainDiffL64Q27), pState->timeConstantUL32Q31[j], 1); // Q27 + // Since timeconstant will always be less than 0 (most significant bit always be zero), + // therefore we can replace signed-unsigned multiplication with signed-signed multiplication. + currDrcGainL64Q27 = s64_add_s64_s64(currDrcGainL64Q27, pState->instGainUL64Q27[j]); + pState->gainUL32Q15[j] = s32_saturate_s64(s64_shl_s64(currDrcGainL64Q27, -12)); + pState->instGainUL64Q27[j] = currDrcGainL64Q27; + pState->currState[j] = currState; +} +#endif diff --git a/modules/processing/gain_control/drc/lib/stub_src/CDrcLib_stub.cpp b/modules/processing/gain_control/drc/lib/stub_src/CDrcLib_stub.cpp new file mode 100644 index 0000000..70d312e --- /dev/null +++ b/modules/processing/gain_control/drc/lib/stub_src/CDrcLib_stub.cpp @@ -0,0 +1,59 @@ +/*============================================================================ +FILE: CDrcLib_stub.cpp + +OVERVIEW: Implements the drciter algorithm. + +DEPENDENCIES: None + +Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. +SPDX-License-Identifier: BSD-3-Clause +============================================================================*/ + +/*---------------------------------------------------------------------------- + * Include Files + * -------------------------------------------------------------------------*/ + +#include "CDrcLib.h" + +/*---------------------------------------------------------------------------- +* Function Definitions +* -------------------------------------------------------------------------*/ +CDrcLib::CDrcLib() +{ + m_drcCfgInt.numChannel = NUM_CHANNEL_DEFAULT; + m_drcData.currState[0] = 0; + m_aGainL32Q15[0][0] = 0; + m_aRmsStateL32[0] = 0; + m_delayBufferLeftL16[0] = 0; + m_delayBufferRightL16[0] = 0; + fnpProcess = NULL; +} + + +PPStatus CDrcLib::Initialize (DrcConfig &cfg) +{ + return PPFAILURE; +} + +PPStatus CDrcLib::ReInitialize (DrcConfig &cfg) +{ + return PPFAILURE; +} + +void CDrcLib::Reset () +{ + +} + + +void CDrcLib::Process ( int16 *pOutPtrL16, + int16 *pOutPtrR16, + int16 *pInPtrL16, + int16 *pInPtrR16, + uint32 nSampleCnt) +{ + return; +} + + + diff --git a/modules/processing/gain_control/iir_mbdrc/api/mbdrc_api.h b/modules/processing/gain_control/iir_mbdrc/api/mbdrc_api.h new file mode 100644 index 0000000..8c4202a --- /dev/null +++ b/modules/processing/gain_control/iir_mbdrc/api/mbdrc_api.h @@ -0,0 +1,754 @@ +#ifndef MBDRC_API_H +#define MBDRC_API_H +/*============================================================================== + @file mbdrc_api.h + @brief This file contains MBDRC parameters +==============================================================================*/ + +/*======================================================================= +* Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. +* SPDX-License-Identifier: BSD-3-Clause +=========================================================================*/ +/*======================================================================== + Edit History + + when who what, where, why + -------- --- ------------------------------------------------------- + 10/10/18 akr Created File . + ========================================================================== */ + +/*------------------------------------------------------------------------ + * Include files + * -----------------------------------------------------------------------*/ +#include "module_cmn_api.h" + +/** + @h2xml_title1 {Multiband Dynamic Range Control (MBDRC) API} + @h2xml_title_agile_rev {Multiband Dynamic Range Control (MBDRC) API} + @h2xml_title_date {October 10, 2018} + */ +/** + @h2xmlx_xmlNumberFormat {int} +*/ + +/*============================================================================== + Constants +==============================================================================*/ +#define CAPI_IIR_MBDRC_MAX_PORT 1 + +#define CAPI_IIR_MBDRC_STACK_SIZE 4096 + +#define IIR_MAX_COEFFS_PER_BAND 10 + +#define IIR_MBDRC_MAX_BANDS 10 + +#define MODULE_ID_IIR_MBDRC 0x07001017 + +#ifdef PROD_SPECIFIC_MAX_CH +#define CAPI_IIR_MBDRC_MAX_CHANNELS 128 +#else +#define CAPI_IIR_MBDRC_MAX_CHANNELS 32 +#endif + +/** + @h2xmlm_module {"MODULE_ID_IIR_MBDRC", + MODULE_ID_IIR_MBDRC} + @h2xmlm_displayName {"IIR MBDRC"} + @h2xmlm_modSearchKeys{drc, Audio} + @h2xmlm_toolPolicy {Calibration} + @h2xmlm_description {Multiband Dynamic Range Control Module \n + + - This module supports the following parameter IDs: \n + - #PARAM_ID_IIR_MBDRC_CONFIG_PARAMS \n + - #PARAM_ID_IIR_MBDRC_FILTER_XOVER_FREQS \n + - #PARAM_ID_MODULE_ENABLE \n + +* Supported Input Media Format: \n +* - Data Format : FIXED_POINT \n +* - fmt_id : Don't care \n +* - Sample Rates : Any \n +* - Number of channels : 1 to 128 (for certain products this module supports only 32 channels) \n +* - Channel type : 1 to 128 \n +* - Bits per sample : 16, 32 \n +* - Q format : 15, 27 \n +* - Interleaving : de-interleaved unpacked \n +* - Signed/unsigned : Signed } + + @h2xmlm_dataMaxInputPorts {CAPI_IIR_MBDRC_MAX_PORT} + @h2xmlm_dataInputPorts {IN=2} + @h2xmlm_dataMaxOutputPorts {CAPI_IIR_MBDRC_MAX_PORT} + @h2xmlm_dataOutputPorts {OUT=1} + @h2xmlm_supportedContTypes {APM_CONTAINER_TYPE_SC, APM_CONTAINER_TYPE_GC} + @h2xmlm_isOffloadable {true} + @h2xmlm_stackSize {CAPI_IIR_MBDRC_STACK_SIZE} + @h2xmlm_ToolPolicy {Calibration} + @{ <-- Start of the Module --> +*/ + +/* ID of the MBDRC Configuration parameter used by MODULE_ID_IIR_MBDRC.*/ +#define PARAM_ID_IIR_MBDRC_CONFIG_PARAMS 0x08001028 + +#include "spf_begin_pack.h" +/** @h2xmlp_subStruct */ +struct subband_drc_config_params_t +{ + + int16_t drc_mode; + /**< @h2xmle_description {Specifies whether DRC mode is bypassed for subbands.} + @h2xmle_rangeList {"Disable"=0; "Enable"=1} + @h2xmle_default {1} */ + + int16_t drc_linked_flag; + /**< @h2xmle_description {Specifies whether all stereo channels have the same applied dynamics + or if they process their dynamics independently.} + @h2xmle_rangeList {"Not linked,channels process the dynamics independently" = 0; + "Linked,channels have the same applied dynamics" = 1} + @h2xmle_default {1} */ + + int16_t drc_down_sample_level; + /**< @h2xmle_description {DRC down sample level.} + @h2xmle_default {1} + @h2xmle_range {1..16}*/ + + uint16_t drc_rms_time_avg_const; + /**< @h2xmle_description {RMS signal energy time-averaging constant.} + @h2xmle_default {298} + @h2xmle_range {0..65535} + @h2xmle_dataFormat {Q16} */ + + uint16_t drc_makeup_gain; + /**< @h2xmle_description {DRC makeup gain in decibels.} + @h2xmle_default {4096} + @h2xmle_range {258..64917} + @h2xmle_dataFormat {Q12} */ + + /* Down expander settings */ + + int16_t down_expdr_threshold; + /**< @h2xmle_description {Down expander threshold.} + Its value must be: (*/ + +#endif diff --git a/modules/processing/gain_control/iir_mbdrc/bin/arm/libiir_mbdrc.a b/modules/processing/gain_control/iir_mbdrc/bin/arm/libiir_mbdrc.a new file mode 100644 index 0000000000000000000000000000000000000000..e474add6ded17e3905b1697ab950ab7f9c301943 GIT binary patch literal 499152 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

+R1Q~0?V&@`W1OAdx(rV+Yn9joi0$j| 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 O-%;pygaz~o^8H`DHluU^ literal 0 HcmV?d00001 diff --git a/modules/processing/gain_control/iir_mbdrc/build/CMakeLists.txt b/modules/processing/gain_control/iir_mbdrc/build/CMakeLists.txt new file mode 100644 index 0000000..92ddacb --- /dev/null +++ b/modules/processing/gain_control/iir_mbdrc/build/CMakeLists.txt @@ -0,0 +1,40 @@ +#[[ + @file CMakeLists.txt + + @brief + + @copyright + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause + + // clang-format off + $Header: $ + // clang-format on +]] +cmake_minimum_required(VERSION 3.10) + +#Include directories +set(iir_mbdrc_includes + ${LIB_ROOT}/api + ${LIB_ROOT}/inc + ../../../../cmn/common/utils/inc + ../../drc/lib/inc + ../../limiter/lib/inc + ) + + +spf_module_sources( + KCONFIG CONFIG_IIR_MBDRC + NAME iir_mbdrc + MAJOR_VER 1 + MINOR_VER 0 + AMDB_ITYPE "capi" + AMDB_MTYPE "pp" + AMDB_MID "0x07001017" + AMDB_TAG "capi_iir_mbdrc" + AMDB_MOD_NAME "MODULE_ID_IIR_MBDRC" + INCLUDES ${iir_mbdrc_includes} + H2XML_HEADERS "${LIB_ROOT}/api/mbdrc_api.h" + CFLAGS "" + STATIC_LIB_PATH "${LIB_ROOT}/bin/arm/libiir_mbdrc.a" +) diff --git a/modules/processing/gain_control/iir_mbdrc/inc/capi_iir_mbdrc.h b/modules/processing/gain_control/iir_mbdrc/inc/capi_iir_mbdrc.h new file mode 100644 index 0000000..967a6de --- /dev/null +++ b/modules/processing/gain_control/iir_mbdrc/inc/capi_iir_mbdrc.h @@ -0,0 +1,43 @@ +/* ===================================================================== + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause + * =====================================================================*/ + +/** + * @file capi_iir_mbdrc.h + * + */ + +#ifndef CAPI_IIR_MBDRC_H +#define CAPI_IIR_MBDRC_H + +#include "capi.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Get static properties of mbdrc module such as + * memory, stack requirements etc. + * See Elite_CAPI.h for more details. + */ +capi_err_t capi_iir_mbdrc_get_static_properties( + capi_proplist_t* init_set_properties, + capi_proplist_t* static_properties); + + +/** + * Instantiates(and allocates) the module memory. + * See Elite_CAPI.h for more details. + */ +capi_err_t capi_iir_mbdrc_init( + capi_t* _pif, + capi_proplist_t* init_set_properties); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/modules/processing/gain_control/iir_mbdrc/inc/capi_iir_mbdrc_utils.h b/modules/processing/gain_control/iir_mbdrc/inc/capi_iir_mbdrc_utils.h new file mode 100644 index 0000000..aaf5543 --- /dev/null +++ b/modules/processing/gain_control/iir_mbdrc/inc/capi_iir_mbdrc_utils.h @@ -0,0 +1,369 @@ +/* ======================================================================== */ +/** + @file capi_iir_mbdrc_utils.h + + Header file to implement utilities for MBDRC audio Post + Processing module + */ + +/* ========================================================================= + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause + ========================================================================== */ + +#ifndef CAPI_IIR_MBDRC_UTILS_H +#define CAPI_IIR_MBDRC_UTILS_H + +/*------------------------------------------------------------------------ + * Include files + * -----------------------------------------------------------------------*/ +#include "capi.h" +#include "capi_cmn.h" +#ifdef CAPI_STANDALONE +#include "capi_util.h" +#else +#include "posal.h" +#endif + +#include "capi_iir_mbdrc.h" +#include "iir_mbdrc_api.h" +#include "iir_mbdrc_calibration_api.h" +#include "mbdrc_api.h" +#include "stringl.h" +/*------------------------------------------------------------------------ + * Macros, Defines, Type declarations + * -----------------------------------------------------------------------*/ + +#define MIID_UNKNOWN 0 + +#define IIR_MBDRC_MSG_PREFIX "IIR_MBDRC:[%lX] " +#define IIR_MBDRC_MSG(ID, xx_ss_mask, xx_fmt, ...) AR_MSG(xx_ss_mask, IIR_MBDRC_MSG_PREFIX xx_fmt, ID, ##__VA_ARGS__) + +// Default values for iir_mbdrc static parameters +static const uint32 CAPI_IIR_MBDRC_DEFAULT_SAMPLING_RATE = 48000; // sampling rate +static const uint32 CAPI_IIR_MBDRC_DEFAULT_NUM_CHANNELS = 1; // Mono channel +static const uint32 CAPI_IIR_MBDRC_DEFAULT_BLOCK_SIZE = 480; // 480 = 10ms samples at 48KHz + +#define IIR_MBDRC_MAX_MSIIR_STATES_MEM (32) +// max # of even allpass stage +#define IIR_MBDRC_EVEN_AP_STAGE 3 +// max # of odd allpass stage +#define IIR_MBDRC_ODD_AP_STAGE 2 + +#define CAPI_IIR_MBDRC_ALIGN_TO_8(x) ((((uint32_t)(x) + 7) >> 3) << 3) +#define ALIGN8(o) (((o) + 7) & (~7)) +#define CAPI_IIR_MBDRC_ALIGN_4_BYTE(x) (((x) + 3) & (0xFFFFFFFC)) + +// Limiter default values +static const int32 CAPI_IIR_MBDRC_LIM_THRESH_16BIT_VAL = 93945856; +static const int32 CAPI_IIR_MBDRC_LIM_MAKEUP_GAIN_VAL = 256; +static const int32 CAPI_IIR_MBDRC_LIM_GC_VAL = 32440; +static const int32 CAPI_IIR_MBDRC_LIM_MAX_WAIT_VAL = 82; +static const int32 CAPI_IIR_MBDRC_LIM_GAIN_ATTACK_VAL = 188099735; +static const int32 CAPI_IIR_MBDRC_LIM_GAIN_RELEASE_VAL = 32559427; +static const int32 CAPI_IIR_MBDRC_LIM_RELEASE_COEF_VAL = 32768; +static const int32 CAPI_IIR_MBDRC_LIM_ATTACK_COEF_VAL = 32768; +static const int32 CAPI_IIR_MBDRC_LIM_HARD_THRESH_16BIT_VAL = 93945856; +static const int32 CAPI_IIR_MBDRC_LIM_HISTORY_WINLEN_VAL = 262; +static const int32 CAPI_IIR_MBDRC_LIM_DELAY_VAL = 262; + +static const int32 CAPI_IIR_MBDRC_KPPS_CH1_DW8_VAL = 587860; +static const int32 CAPI_IIR_MBDRC_KPPS_CH2_DW8_VAL = 607254; +static const int32 CAPI_IIR_MBDRC_KPPS_CH6_DW8_VAL = 569251; +static const int32 CAPI_IIR_MBDRC_KPPS_CH8_DW8_VAL = 539941; +static const int32 CAPI_IIR_MBDRC_KPPS_CH1_DW8_band_1_2_VAL = 503647; +static const int32 CAPI_IIR_MBDRC_KPPS_CH2_DW8_band_1_2_VAL = 508655; +static const int32 CAPI_IIR_MBDRC_KPPS_CH6_DW8_band_1_2_VAL = 455077; +static const int32 CAPI_IIR_MBDRC_KPPS_CH8_DW8_band_1_2_VAL = 445501; + +static const int32 CAPI_IIR_MBDRC_KPPS_CH1_DW1_link1_VAL = 865519; +static const int32 CAPI_IIR_MBDRC_KPPS_CH2_DW1_link1_VAL = 740111; +static const int32 CAPI_IIR_MBDRC_KPPS_CH6_DW1_link1_VAL = 609267; +static const int32 CAPI_IIR_MBDRC_KPPS_CH8_DW1_link1_VAL = 591798; +static const int32 CAPI_IIR_MBDRC_KPPS_CH1_DW1_link1_band_1_2_VAL = 781348; +static const int32 CAPI_IIR_MBDRC_KPPS_CH2_DW1_link1_band_1_2_VAL = 653223; +static const int32 CAPI_IIR_MBDRC_KPPS_CH6_DW1_link1_band_1_2_VAL = 500676; +static const int32 CAPI_IIR_MBDRC_KPPS_CH8_DW1_link1_band_1_2_VAL = 480502; + +static const int32 CAPI_IIR_MBDRC_KPPS_CH1_DW1_link0_VAL = 865519; +static const int32 CAPI_IIR_MBDRC_KPPS_CH2_DW1_link0_VAL = 854974; +static const int32 CAPI_IIR_MBDRC_KPPS_CH6_DW1_link0_VAL = 826218; +static const int32 CAPI_IIR_MBDRC_KPPS_CH8_DW1_link0_VAL = 843466; +static const int32 CAPI_IIR_MBDRC_KPPS_CH1_DW1_link0_band_1_2_VAL = 792501; +static const int32 CAPI_IIR_MBDRC_KPPS_CH2_DW1_link0_band_1_2_VAL = 792973; +static const int32 CAPI_IIR_MBDRC_KPPS_CH6_DW1_link0_band_1_2_VAL = 738497; +static const int32 CAPI_IIR_MBDRC_KPPS_CH8_DW1_link0_band_1_2_VAL = 758090; + +static const uint32 CAPI_IIR_MBDRC_limiter_threshold_min = 1; // 16-bit - -96db , 24-bit - -162db , 32-bit - -162db +static const uint32 CAPI_IIR_MBDRC_limiter_threshold_32bit_max = 2127207634; // 24-bit - 24db , 32-bit - 0db + +/*------------------------------------------------------------------------ + * Structure definitions + * -----------------------------------------------------------------------*/ +typedef struct capi_iir_mbdrc_common_config_struct_t +{ + iir_mbdrc_num_band_t iir_mbdrc_num_band; + iir_mbdrc_limiter_mode_t iir_mbdrc_limiter_mode; + iir_mbdrc_limiter_bypass_t iir_mbdrc_limiter_bypass; + uint32 version; +} capi_iir_mbdrc_common_config_struct_t; + +typedef struct capi_iir_mbdrc_crossover_freqs_t +{ + uint32 ch_idx; // channel index + uint32 crossover_freqs[IIR_MBDRC_MAX_BANDS - 1]; // cross-over frequencies; +} capi_iir_mbdrc_crossover_freqs_t; + +typedef struct capi_iir_mbdrc_per_config_crossover_freqs_struct_t +{ + uint32_t channel_mask_lsb; + uint32_t channel_mask_msb; + capi_iir_mbdrc_crossover_freqs_t iir_mbdrc_crossover_freqs; +} capi_iir_mbdrc_per_config_crossover_freqs_struct_t; + +typedef struct capi_iir_mbdrc_config_struct_t +{ + iir_mbdrc_mute_flags_t iir_mbdrc_mute_flags; + iir_mbdrc_drc_feature_mode_struct_t + * iir_mbdrc_drc_feature_mode_struct; // iir_mbdrc_drc_feature_mode_struct[num_bands] + iir_mbdrc_drc_config_struct_t *iir_mbdrc_drc_cfg; // iir_mbdrc_drc_cfg[num_bands] + iir_mbdrc_limiter_tuning_t iir_mbdrc_limiter_cfg; + iir_mbdrc_iir_config_struct_t *iir_mbdrc_iir_cfg; // iir_mbdrc_iir_cfg[num_bands-1] +} capi_iir_mbdrc_config_struct_t; + +typedef struct capi_iir_mbdrc_per_config_struct_t +{ + uint32_t channel_mask_lsb; // Contains Channel type info + // Least significant bit = 0 => per channel + // calibration disable Least significant bit = 1 => + // per channel calibration enable + uint32_t channel_mask_msb; // Contains Channel type info + capi_iir_mbdrc_config_struct_t iir_mbdrc_cfg; // Has calibration for the above mapped channels. + +} capi_iir_mbdrc_per_config_struct_t; + +typedef struct capi_iir_mbdrc_per_config_t +{ + uint32_t num_config; + uint32_t num_crossover_freqs_config; + capi_iir_mbdrc_per_config_struct_t * iir_mbdrc_per_cfg; // iir_mbdrc_per_cfg[num_config] + capi_iir_mbdrc_per_config_crossover_freqs_struct_t *iir_mbdrc_crossover_freqs_per_cfg; + // iir_mbdrc_crossover_freqs_per_cfg[num_crossover_freqs_config] +} capi_iir_mbdrc_per_config_t; + +typedef struct capi_iir_mbdrc_per_config_crossover_freqs_struct_v2_t +{ + uint32_t channel_map_mask_list[CAPI_CMN_MAX_CHANNEL_MAP_GROUPS]; + capi_iir_mbdrc_crossover_freqs_t iir_mbdrc_crossover_freqs; +} capi_iir_mbdrc_per_config_crossover_freqs_struct_v2_t; + +typedef struct capi_iir_mbdrc_per_config_struct_v2_t +{ + uint32_t channel_map_mask_list[CAPI_CMN_MAX_CHANNEL_MAP_GROUPS]; + capi_iir_mbdrc_config_struct_t iir_mbdrc_cfg; // Has calibration for the above mapped channels. +} capi_iir_mbdrc_per_config_struct_v2_t; + +typedef struct capi_iir_mbdrc_per_config_v2_t +{ + uint32_t num_config; + uint32_t num_crossover_freqs_config; + capi_iir_mbdrc_per_config_struct_v2_t * iir_mbdrc_per_cfg; // iir_mbdrc_per_cfg[num_config] + capi_iir_mbdrc_per_config_crossover_freqs_struct_v2_t *iir_mbdrc_crossover_freqs_per_cfg; + uint32_t cached_xover_freqs_v2_param_size; + uint32_t cached_mbdrc_config_v2_param_size; +} capi_iir_mbdrc_per_config_v2_t; + +typedef struct capi_iir_mbdrc_memory_struct_t +{ + iir_mbdrc_lib_mem_requirements_t mem_req; // MBDRC memory requirements structure + void * mem_ptr; // MBDRC memory pointer + capi_heap_id_t heap_mem; // Heap ID to be used for memory allocation +} iir_mbdrc_memory_struct_t; + +typedef struct capi_iir_mbdrc_event_report_t +{ + capi_event_callback_info_t cb_info; + uint32_t iir_mbdrc_bw; + uint32_t iir_mbdrc_kpps; + uint32_t iir_mbdrc_delay_in_us; + bool_t iir_mbdrc_enable; +} iir_mbdrc_event_report_t; + +typedef enum capi_iir_mbdrc_config_version_t +{ + DEFAULT = 0, + VERSION_V1 = 1, + VERSION_V2 = 2 +} capi_iir_mbdrc_config_version_t; + +#define CAPI_IIR_MBDRC_MF_V2_MIN_SIZE (sizeof(capi_standard_data_format_v2_t) + sizeof(capi_set_get_media_format_t)) + +typedef struct capi_iir_mbdrc_t +{ + capi_vtbl_t * vtbl; + capi_media_fmt_v2_t media_fmt[CAPI_IIR_MBDRC_MAX_PORT]; + capi_iir_mbdrc_event_report_t iir_mbdrc_event_report; + capi_iir_mbdrc_memory_struct_t iir_mbdrc_memory; // contains memory information + iir_mbdrc_lib_t iir_mbdrc_lib; // contains ptr to the total chunk of lib mem + iir_mbdrc_static_struct_t iir_mbdrc_static_cfg; // Stores static config information from media + // format and calibration received during Set param + iir_mbdrc_static_struct_t iir_mbdrc_static_cfg_of_defult_cfg; // Stores static config information + // from media format and default + // calibration + iir_mbdrc_feature_mode_t iir_mbdrc_feature_mode; // Stores MBDRC mode + bool_t is_iir_mbdrc_set_cfg_param_received; // This flag is set to 1 if Set + // param is received or else it is + // set to 0 + bool_t is_iir_mbdrc_media_fmt_received; // This flag is set to 1 if Media format + // is received or else it is set to 0 + bool_t is_iir_mbdrc_library_set_with_default_cfg; // This flag is set to 1 if + // library is set with default + // config, due to non + // availability of calibration + // for all the channel type + // received through Media + // format in Calibration + // received through Set Param + iir_mbdrc_per_channel_calib_mode_t iir_mbdrc_per_channel_calib_mode; // Stores information if per-channel + // calibration is enabled or disabled + capi_iir_mbdrc_common_config_struct_t iir_mbdrc_common_cfg; + capi_iir_mbdrc_per_config_t iir_mbdrc_main_cfg; // Stores the calibration received through set param + capi_iir_mbdrc_per_config_t + iir_mbdrc_default_cfg; // Stores the default calibratio capi_iir_mbdrc_per_config_t iir_mbdrc_main_cfg; // + // Stores the calibration received through set param + capi_iir_mbdrc_per_config_v2_t iir_mbdrc_main_cfg_v2; // Stores the calibration received through set param + capi_iir_mbdrc_per_config_v2_t iir_mbdrc_default_cfg_v2; // Stores the calibration received through set param + uint32_t cached_mbdrc_config_v2_param_size; + uint32_t cached_mbdrc_xover_freqs_v2_param_size; + uint32_t miid; // Module Instance ID + uint64_t input_mf_ch_mask_v1; // Input channel mask + // Input channel mask for v2 config + uint32_t input_mf_ch_mask_list_v2[CAPI_CMN_MAX_CHANNEL_MAP_GROUPS]; + // flag to detect if higher than 63 channel map received or not + bool_t higher_channel_map_present; + + capi_iir_mbdrc_config_version_t cfg_version; + +} capi_iir_mbdrc_t; + +/*------------------------------------------------------------------------ + * Function declarations + * -----------------------------------------------------------------------*/ +// Utility functions + +// Functions to be called from init function. +void capi_iir_mbdrc_init_media_fmt(capi_iir_mbdrc_t *const me_ptr); +void capi_iir_mbdrc_init_events(capi_iir_mbdrc_t *const me_ptr); +capi_err_t capi_iir_mbdrc_init_config(capi_iir_mbdrc_t *const me_ptr); + +// Set and Get property functions. +capi_err_t capi_iir_mbdrc_process_set_properties(capi_iir_mbdrc_t *me_ptr, capi_proplist_t *proplist_ptr); +capi_err_t capi_iir_mbdrc_process_get_properties(capi_iir_mbdrc_t *me_ptr, capi_proplist_t *proplist_ptr); + +// Set and Get Config params functions. +capi_err_t capi_iir_mbdrc_set_config_params(capi_iir_mbdrc_t *me_ptr, const uint32_t param_id, capi_buf_t *params_ptr); +capi_err_t capi_iir_mbdrc_get_config_params(capi_iir_mbdrc_t *me_ptr, const uint32_t param_id, capi_buf_t *params_ptr); + +// Function to raise the events +void capi_iir_mbdrc_raise_events(capi_iir_mbdrc_t *const me_ptr, bool_t media_fmt_update); +capi_err_t capi_iir_mbdrc_check_channel_map_cfg(iir_mbdrc_per_ch_filter_xover_freqs_t *cfg_ptr, uint32_t num_config); +capi_err_t capi_iir_mbdrc_init_default_xover_config(capi_iir_mbdrc_t *me_ptr); +uint64_t capi_iir_mbdrc_calculate_channel_mask(uint32_t channel_mask_lsb, uint32_t channel_mask_msb); +iir_mbdrc_per_channel_calib_mode_t capi_iir_mbdrc_configure_per_channel_calib_mode( + capi_iir_mbdrc_t *me_ptr, + uint64_t channel_map_from_set_config); + +capi_err_t capi_iir_mbdrc_allocate_lib_memory(capi_iir_mbdrc_t *me_ptr, + bool_t force_reinit, + bool_t use_default_static_cfg); + +capi_err_t capi_iir_mbdrc_set_xover_freq_v2_params(capi_iir_mbdrc_t *me_ptr, + const uint32_t param_id, + capi_buf_t * params_ptr); + +capi_err_t capi_iir_mbdrc_validate_multichannel_v2_payload(capi_iir_mbdrc_t *me_ptr, + int8_t * data_ptr, + uint32_t param_id, + uint32_t param_size, + uint32_t num_cfg, + uint32_t * req_payload_size, + uint32_t base_payload_size, + uint32_t per_cfg_base_payload_size); + +capi_err_t capi_iir_mbdrc_validate_per_channel_v2_payload(capi_iir_mbdrc_t *me_ptr, + uint32_t num_cfg, + int8_t * data_ptr, + uint32_t param_size, + uint32_t * required_size_ptr, + uint32_t param_id, + uint32_t base_payload_size, + uint32_t per_cfg_base_payload_size); + +bool_t capi_iir_mbdrc_check_multi_ch_channel_mask_v2_param(uint32_t miid, + uint32_t num_config, + uint32_t param_id, + int8_t * param_ptr, + uint32_t base_payload_size, + uint32_t per_cfg_base_payload_size); + +capi_err_t capi_iir_mbdrc_set_config_v2_params(capi_iir_mbdrc_t *me_ptr, + const uint32_t param_id, + capi_buf_t * params_ptr); + +iir_mbdrc_per_channel_calib_mode_t capi_iir_mbdrc_configure_per_channel_calib_mode_v2(capi_iir_mbdrc_t *me_ptr, + uint32_t *channel_mask_list, + uint32_t channel_group_mask); + +bool_t capi_iir_mbdrc_check_if_non_vol_cal_changed_v2( + capi_iir_mbdrc_t * me_ptr, + capi_buf_t * params_ptr, + int32_t * memory_needs_to_change, + iir_mbdrc_per_channel_calib_mode_t iir_mbdrc_per_channel_calib_mode); + +void capi_iir_mbdrc_get_channel_mask_list_v2(uint32_t temp_channel_mask_list[], + uint32_t *channel_mask_list, + uint32_t channel_group_mask); + +capi_err_t capi_iir_mbdrc_get_xover_freq_v2_params(capi_iir_mbdrc_t *me_ptr, + const uint32_t param_id, + capi_buf_t * params_ptr); + +capi_err_t capi_iir_mbdrc_init_default_xover_config_v2(capi_iir_mbdrc_t *me_ptr); + +capi_err_t capi_iir_mbdrc_init_default_config_v2(capi_iir_mbdrc_t *const me_ptr, + bool_t cache_defaults_to_iir_mbdrc_main_cfg); + +void capi_iir_mbdrc_get_calib_mode_for_v2_cfg(capi_iir_mbdrc_t *me_ptr); + +void capi_iir_mbdrc_check_allocate_and_initialize_lib_v2_cfg(capi_iir_mbdrc_t *me_ptr, + uint32_t num_cfg, + uint32_t channels_allocated, + uint32_t *matched_ptr); + +capi_err_t capi_iir_mbdrc_init_lib_v1(capi_iir_mbdrc_t *me_ptr, uint32_t matched); + +capi_err_t capi_iir_mbdrc_init_lib_v2(capi_iir_mbdrc_t *me_ptr, uint32_t matched); + +void capi_iir_mbdrc_post_process_lib_initialization_v1(capi_iir_mbdrc_t *me_ptr); + +void capi_iir_mbdrc_post_process_lib_initialization_v2(capi_iir_mbdrc_t *me_ptr); + +capi_err_t capi_iir_mbdrc_set_params_to_library_v2(capi_iir_mbdrc_t *me_ptr, bool_t use_default_static_cfg); + +void capi_iir_mbdrc_update_drc_flags_and_ch_mask_v2(capi_iir_mbdrc_t *me_ptr, + const uint32_t num_bands, + bool_t * downsample_level_flag_1, + bool_t * drc_linked_flag, + const uint32_t num_channel); + +void capi_iir_mbdrc_check_allocate_and_initialize_lib_v2_cfg(capi_iir_mbdrc_t *me_ptr, + uint32_t channels_allocated, + uint32_t * matched); + +capi_err_t capi_iir_mbdrc_get_config_params_v2(capi_iir_mbdrc_t *const me_ptr, + uint32_t param_id, + capi_buf_t *params_ptr); + +uint32_t capi_iir_mbdrc_calculate_cached_size_for_default_v2_param(uint32_t num_bands, + uint32_t num_config); +#endif diff --git a/modules/processing/gain_control/iir_mbdrc/inc/iir_mbdrc.h b/modules/processing/gain_control/iir_mbdrc/inc/iir_mbdrc.h new file mode 100644 index 0000000..ddc581d --- /dev/null +++ b/modules/processing/gain_control/iir_mbdrc/inc/iir_mbdrc.h @@ -0,0 +1,149 @@ +/* ========================================================================= + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause + ========================================================================== */ + +#ifndef IIR_MBDRC_H +#define IIR_MBDRC_H + +#include "iir_mbdrc_api.h" +#include "drc_api.h" +#include "limiter_api.h" +#include "audio_dsp.h" +#include "audio_apiir_df1_opt.h" + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/*---------------------------------------------------------------------------- + * Preprocessor Definitions and Constants + * -------------------------------------------------------------------------*/ +#define IIR_MBDRC_LIB_VER (0x0400010000000000) // lib version : 4.0_1.0.0 + // external_major.external_minor.major.minor.revision.custom_alphabet.custom_number.reserved) + // (8.8.8.8.8.8.8.8 bits) + + +static const uint32 eq_max_stack_size = 600; // worst case stack mem in bytes +#define ALIGN8(o) (((o)+7)&(~7)) + + +#define IIR_MBDRC_TIME_CONST 0x1BF7A00 // UL32Q31, same as DN_EXPA_RELEASE_DEFAULT + +/* define IIR-related constants */ +// max states required per band +#define MAX_MSIIR_STATES_MEM (32) +// max # of even allpass stage +#define EVEN_AP_STAGE 3 +// max # of odd allpass stage +#define ODD_AP_STAGE 2 +// use fixed Q27 for all coef +#define IIR_COEFFS_TARGET_Q_FACTOR 27 +// for allpass biquad iir, only b0 and b1 are needed +#define BIQUAD_APIIR_COEFF_LENGTH 2 + +#define MBDRC_NUM_BAND_DEFAULT 1 +/*---------------------------------------------------------------------------- + * Type Declarations + * -------------------------------------------------------------------------*/ + +// iir_mbdrc states struct +typedef struct iir_mbdrc_states_mem_t +{ + // dynamic mem allocation for mute_flags[num_channels][IIR_MBDRC_MAX_BANDS] + int32 **mute_flags; + + // dynamic mem allocation for iir_mbdrc_cross_over_freqs[num_channels][IIR_MBDRC_MAX_BANDS-1] + uint32 **iir_mbdrc_cross_over_freqs; + + // dynamic mem allocation for drc_enable[num_channels][IIR_MBDRC_MAX_BANDS] + uint16 **drc_enable; + + // dynamic mem allocation for iir_mbdrc_makeupgain[num_channels][IIR_MBDRC_MAX_BANDS] + uint16 **iir_mbdrc_makeupgain; + + // dynamic mem allocation for iir_mbdrc_inst_makeupgain[num_channels][IIR_MBDRC_MAX_BANDS] + uint16 **iir_mbdrc_inst_makeupgain; + + uint32 num_band; +} iir_mbdrc_states_mem_t; + + +// iir_mbdrc internal memory struct +typedef struct iir_mbdrc_misc_scratch_buf_t +{ + // LPF output; shared by all bands + // dynamic mem allocation for int32 *subBandLPBuf[IIR_MBDRC_MAX_CHANNELS] + int32 **subBandLPBuf; + + // HPF output, which is input of next LPF; shared by all bands + // dynamic mem allocation for int32 *subBandHPBuf[IIR_MBDRC_MAX_CHANNELS] + int32 **subBandHPBuf; + + // IIR filter bank output, which is used for 16-bit case; shared by all bands + // dynamic mem allocation for int16 *internalOutBuf16[IIR_MBDRC_MAX_CHANNELS] + int16 **internalOutBuf16; + + // mixer output, which is limiter input; shared by all bands + // dynamic mem allocation for *internalOutBuf32[IIR_MBDRC_MAX_CHANNELS] + int32 **internalOutBuf32; + + // shared by all bands/channels + int32 *scatch_buf; + +} iir_mbdrc_misc_scratch_buf_t; + +// iir filter structure +// array is allocated for worse case (9-order IIR design) +typedef struct iir_filter_t +{ + uint32 num_stages[2]; // num_stages[0]: even filter stages; + // num_stages[1]: odd filter stages + + /* according to allpass biquad filter's transfer function: */ + /* H_ap(z) = (b0 + b1*z^-1 + z^-2)/(1 + b1*z^-1 + b0*z^-2) */ + /* only b0 and b1 needs to be saved */ + /* Besides, the 3-rd stage of even allpass filter is 1st order IIR */ + /* H_even_3rd(z) = (b0 + z^-1)/(1 + b0*z^-1) */ + /* only b0 needs to be saved */ + /* iir_coeffs store order is: */ + /* [b0_even1 b1_even1 | b0_even2 b1_even2 | b0_even3 b1_even3 | ...*/ + /* ...b0_odd1 b1_odd1 | b0_odd2 b1_odd2] */ + int32 iir_coeffs[MSIIR_MAX_COEFFS_PER_BAND]; + + /* each biquad IIR needs 4 mem, 1st order IIR needs 2 mem */ + /* states store order is: */ + /* [4 mem for even ap1 | 4 mem for even ap2 | ... */ + /* ... 4 mem for 1st order iir | 4 mem for odd ap1 | ... */ + /* ... 4 mem for odd ap2 | ... */ + /* ... 3 * (4 mem for delay apx)] */ + int32 states[MAX_MSIIR_STATES_MEM]; +} iir_filter_t; + + +// IIR_MBDRC lib mem structure +typedef struct iir_mbdrc_lib_mem_t +{ + iir_mbdrc_static_struct_t *iir_mbdrc_static_struct_ptr; + iir_mbdrc_feature_mode_t *iir_mbdrc_feature_mode_ptr; + iir_mbdrc_per_channel_calib_mode_t *iir_mbdrc_per_channel_calib_mode_ptr; + + // dynamic mem allocation for DRC library + drc_lib_t *drc_lib_linked_mem; // DRC when per-channel mode is enabled: drc_lib_linked_mem[IIR_MBDRC_MAX_BANDS] + drc_lib_t **drc_lib_mem; // DRC when per-channel mode is disabled: drc_lib_mem[num_channel][IIR_MBDRC_MAX_BANDS] + limiter_lib_t limiter_lib_mem; // multi ch ready + iir_mbdrc_states_mem_t *iir_mbdrc_states_mem_ptr; + iir_mbdrc_misc_scratch_buf_t *iir_mbdrc_misc_scratch_buf_ptr; + + // dynamic mem allocation for iir_filter_t *iir_filter_struct_ptr_array[IIR_MBDRC_MAX_BANDS-1][IIR_MBDRC_MAX_CHANNELS] + iir_filter_t ***iir_filter_struct_ptr_array; + +} iir_mbdrc_lib_mem_t; + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif // IIR_MBDRC_H diff --git a/modules/processing/gain_control/iir_mbdrc/inc/iir_mbdrc_api.h b/modules/processing/gain_control/iir_mbdrc/inc/iir_mbdrc_api.h new file mode 100644 index 0000000..2bebb03 --- /dev/null +++ b/modules/processing/gain_control/iir_mbdrc/inc/iir_mbdrc_api.h @@ -0,0 +1,108 @@ +/* ========================================================================= + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause + ========================================================================== */ + +#ifndef IIR_MBDRC_API_H +#define IIR_MBDRC_API_H + +#include "iir_mbdrc_calibration_api.h" +#include "iir_mbdrc_function_defines.h" +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/*---------------------------------------------------------------------------- + * Type Declarations + * -------------------------------------------------------------------------*/ +typedef enum IIR_MBDRC_RESULT +{ + IIR_MBDRC_SUCCESS = 0, + IIR_MBDRC_FAILURE, + IIR_MBDRC_MEMERROR +}IIR_MBDRC_RESULT; + +//IIR_MBDRC static params structure +typedef struct iir_mbdrc_static_struct_t +{ + uint32_t data_width; // 16 or 32 + uint32_t sample_rate; // Hz + uint32_t num_channel; // num of channel; up to 8 + uint32_t *drc_delay_samples; // dynamic mem allocation for drc_delay_samples * max_num_band; + // samples per channel in Q0 + uint32_t max_num_band; // max num of band; 1 ~ 10 + uint32_t limiter_delay_secs; // seconds per channel in Q15, the same for all channels + uint32_t max_block_size; // for limiter + uint32_t limiter_history_winlen; // length of history window (Q15 value of sec) +} iir_mbdrc_static_struct_t; + + +// IIR_MBDRC lib structure +typedef struct iir_mbdrc_lib_t +{ + void* iir_mbdrc_lib_mem_ptr; // ptr to the total chunk of lib mem +} iir_mbdrc_lib_t; + + +// IIR_MBDRC lib mem requirements structure +typedef struct iir_mbdrc_lib_mem_requirements_t +{ + uint32_t iir_mbdrc_lib_mem_size; // size of the lib mem pointed by lib_mem_ptr + uint32_t iir_mbdrc_lib_stack_size; // stack mem size +} iir_mbdrc_lib_mem_requirements_t; + + +/*---------------------------------------------------------------------------- + * Function Declarations and Documentation + * -------------------------------------------------------------------------*/ + +// get lib mem req +// iir_mbdrc_lib_mem_requirements_ptr: [out] Pointer to lib mem requirements structure +// iir_mbdrc_static_struct_ptr: [in] Pointer to static structure +IIR_MBDRC_RESULT iir_mbdrc_get_mem_req(iir_mbdrc_lib_mem_requirements_t *iir_mbdrc_lib_mem_requirements_ptr, iir_mbdrc_static_struct_t *iir_mbdrc_static_struct_ptr); + + +// partition and init the mem with default values +// iir_mbdrc_lib_ptr: [in, out] Pointer to lib structure +// iir_mbdrc_static_struct_ptr: [in] Pointer to static structure +// mem_ptr: [in] Pointer to lib memory +// mem_size: [in] Size of the memory pointed by mem_ptr +IIR_MBDRC_RESULT iir_mbdrc_init_memory(iir_mbdrc_lib_t *iir_mbdrc_lib_ptr, iir_mbdrc_static_struct_t *iir_mbdrc_static_struct_ptr, int8_t *mem_ptr, uint32_t mem_size); + + +// retrieve params from lib mem +// iir_mbdrc_lib_ptr: [in] Pointer to lib structure +// param_id: [in] ID of the param +// mem_ptr: [in/out] Pointer to the memory where params are to be stored +// mem_size:[in] Size of the memory pointed by mem_ptr +// param_size_ptr: [out] Pointer to param size which indicates the size of the retrieved param(s) +IIR_MBDRC_RESULT iir_mbdrc_get_param(iir_mbdrc_lib_t *iir_mbdrc_lib_ptr, uint32_t param_id, int8_t *mem_ptr, uint32_t mem_size, uint32_t *param_size_ptr); + + +// set the params in the lib mem +// iir_mbdrc_lib_ptr: [in, out] Pointer to lib structure +// param_id: [in] ID of the param +// mem_ptr: [in] Pointer to the memory where the values stored are used to set up the params in the lib memory +// mem_size:[in] Size of the memory pointed by mem_ptr +IIR_MBDRC_RESULT iir_mbdrc_set_param(iir_mbdrc_lib_t *iir_mbdrc_lib_ptr, uint32_t param_id, int8_t *mem_ptr, uint32_t mem_size); + + +// IIR_MBDRC processing of de-interleaved multi-channel audio signal +// iir_mbdrc_lib_ptr: [in] Pointer to lib structure +// out_ptr: [out] Pointer to de-interleaved multi-channel output PCM samples +// in_ptr: [in] Pointer to de-interleaved multi-channel input PCM samples +// block_size: [in] Number of samples to be processed per channel +IIR_MBDRC_RESULT iir_mbdrc_process(iir_mbdrc_lib_t *iir_mbdrc_lib_ptr, int8_t *out_ptr[], int8_t *in_ptr[], uint32_t block_size); + + +#ifdef IIR_MBDRC_SPLITFILTER32_LOOP_ASM +void iir_mbdrc_SplitFilter32_loop(int32_t *tmpOddOut, + int32_t *tmpEvenOut, + uint32_t nSampleCnt); +#endif +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif // IIR_MBDRC_API_H diff --git a/modules/processing/gain_control/iir_mbdrc/inc/iir_mbdrc_calibration_api.h b/modules/processing/gain_control/iir_mbdrc/inc/iir_mbdrc_calibration_api.h new file mode 100644 index 0000000..d0f4d5e --- /dev/null +++ b/modules/processing/gain_control/iir_mbdrc/inc/iir_mbdrc_calibration_api.h @@ -0,0 +1,195 @@ +/* ========================================================================= + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause + ========================================================================== */ + +#ifndef IIR_MBDRC_CALIBRATION_API +#define IIR_MBDRC_CALIBRATION_API + +#include "ar_defs.h" +#include "iir_mbdrc_api.h" +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/*---------------------------------------------------------------------------- + * Preprocessor Definitions and Constants + * -------------------------------------------------------------------------*/ +#define MSIIR_MAX_COEFFS_PER_BAND (10) // # of coeffs needed per filter block + // = #of biquad coeffs * (3 even stages + 2 odd stages) + +/*---------------------------------------------------------------------------- + * Pramameter ID Definitions + * -------------------------------------------------------------------------*/ + +// param id to get lib version +#define IIR_MBDRC_PARAM_GET_LIB_VER (0) // read only +typedef int64_t iir_mbdrc_lib_ver_t; // lib version + // external_major.external_minor.major.minor.revision.custom_alphabet.custom_number.reserved) + // (8.8.8.8.8.8.8.8 bits) + +// this param id is for iir_mbdrc_feature_mode_t +#define IIR_MBDRC_PARAM_FEATURE_MODE (1) // read/write; apply to all channels +typedef enum iir_mbdrc_mode_t +{ + IIR_MBDRC_BYPASSED = 0, // IIR_MBDRC processing bypassed; no IIR_MBDRC processing + IIR_MBDRC_ENABLED // IIR_MBDRC processing enabled; normal IIR_MBDRC processing +} iir_mbdrc_mode_t; +typedef iir_mbdrc_mode_t iir_mbdrc_feature_mode_t; + +// param id to get/set actual num of band +#define IIR_MBDRC_PARAM_NUM_BAND (2) // read/write; apply to all channels; +typedef uint32_t iir_mbdrc_num_band_t; + +// param id to get/set mute flags +#define IIR_MBDRC_PARAM_MUTE_FLAGS (3) // read/write; +typedef struct iir_mbdrc_mute_flags_t +{ + uint32_t ch_idx; // channel index + int32_t *mute_flags; // total mute_flag[num_band] per channel +} iir_mbdrc_mute_flags_t; + // read/write the number of band flags + + +// this param id is for iir_mbdrc_drc_feature_mode_struct_t +#define IIR_MBDRC_PARAM_DRC_FEATURE_MODE (4) // read/write; apply to all channels +typedef enum iir_mbdrc_drc_mode_t +{ + IIR_MBDRC_DRC_BYPASSED = 0, // DRC processing bypassed; + // no DRC processing and only delay is implemented + IIR_MBDRC_DRC_ENABLED, // DRC processing enabled; normal DRC processing +} iir_mbdrc_drc_mode_t; + +typedef struct iir_mbdrc_drc_feature_mode_struct_t +{ + uint32_t ch_idx; // channel index: 0 ~ IIR_MBDRC_MAX_BANDS-1 + uint32_t band_index; // band index: 0 ~ IIR_MBDRC_MAX_CHANNELS-1 (apply same mode to entire channels) + iir_mbdrc_drc_mode_t iir_mbdrc_drc_feature_mode; // 1 is with DRC processing; + // 0 is no DRC processing + // (bypassed, only delay is implemented) +} iir_mbdrc_drc_feature_mode_struct_t; + + +// this param id is for iir_mbdrc_drc_config_struct_t +#define IIR_MBDRC_PARAM_DRC_CONFIG (5) // read/write +typedef struct iir_mbdrc_drc_config_t +{ + // below two should not change during Reinit + int16_t channel_linked; // Q0 channel mode -- Linked(1) or Not-Linked(0); + int16_t down_sample_level; // Q0 Down Sample Level to save MIPS + + uint16_t rms_tav; // Q16 Time Constant used to compute Input RMS + uint16_t make_up_gain; // Q12 Makeup Gain Value + + int16_t dn_expa_threshold; // Q7 downward expansion threshold + int16_t dn_expa_slope; // Q8 downward expansion slope + uint32_t dn_expa_attack; // Q31 downward expansion attack time + uint32_t dn_expa_release; // Q31 downward expansion release time + int32_t dn_expa_min_gain_db; // Q23 downward expansion minimum gain in dB + uint16_t dn_expa_hysterisis; // Q14 downward expansion hysterisis time + + int16_t up_comp_threshold; // Q7 upward compression threshold + uint32_t up_comp_attack; // Q31 upward compression attack time + uint32_t up_comp_release; // Q31 upward compression release time + uint16_t up_comp_slope; // Q8 upward compression slope + uint16_t up_comp_hysterisis; // Q14 upward compression hysterisis time + + int16_t dn_comp_threshold; // Q7 downward compression threshold + uint16_t dn_comp_slope; // Q8 downward compression slope + uint32_t dn_comp_attack; // Q31 downward compression attack time + uint32_t dn_comp_release; // Q31 downward compression release time + uint16_t dn_comp_hysterisis; // Q14 downward compression hysterisis time + + int16_t reserved; + +} iir_mbdrc_drc_config_t; + +typedef struct iir_mbdrc_drc_config_struct_t +{ + uint32_t ch_idx; // channel index: 0 ~ IIR_MBDRC_MAX_BANDS-1 + uint32_t band_index; // band index: 0 ~ IIR_MBDRC_MAX_CHANNELS-1 + iir_mbdrc_drc_config_t iir_mbdrc_drc_config; +} iir_mbdrc_drc_config_struct_t; + + +// param id for get/set limiter mode +#define IIR_MBDRC_PARAM_LIMITER_MODE (6) // read/write +typedef enum iir_mbdrc_limiter_mode_t +{ + LIM_MAKEUPGAIN_ONLY = 0, + LIM_NORMAL_PROC +} iir_mbdrc_limiter_mode_t; + + +// param id for get/set limiter bypass mode +// this id itself has been verified but currently IIR_MBDRC +// does not use limiter in bypass mode + +// this id is here to maintain consistency under unified +// API guideline since limiter lib has this param id + +// this id can be used for new feature support in future +#define IIR_MBDRC_PARAM_LIMITER_BYPASS (7) // read/write +typedef int32_t iir_mbdrc_limiter_bypass_t; + + +// param id for limiter cfg params +#define IIR_MBDRC_PARAM_LIMITER_CONFIG (8) // read/write +typedef struct iir_mbdrc_limiter_tuning_t +{ + int32_t ch_idx; // channel index + int32_t threshold; // threshold (16bit: Q15; 32bit:Q27) + int32_t makeup_gain; // make up gain (Q8) + int32_t gc; // recovery const (Q15) + int32_t max_wait; // max wait (Q15 value of sec) + + uint32_t gain_attack; // limiter gain attack time (Q31) + uint32_t gain_release; // limiter gain release time (Q31) + uint32_t attack_coef; // limiter gain attack time speed coef (Q15) + uint32_t release_coef; // limiter gain release time speed coef (Q15) + int32_t hard_threshold; // threshold for hard limiter (16bit: Q15; 32bit:Q27) +} iir_mbdrc_limiter_tuning_t; + +// param id for IIR coeffs +// write only, only set_param is needed since +// there is no menu to do get_param in ACDB +#define IIR_MBDRC_PARAM_MSIIR_CONFIG (9) + +typedef struct iir_mbdrc_iir_config_struct_t { + uint32_t ch_idx; // channel index + uint32_t band_index; // 0 ~ 9 + uint32_t num_stages[2]; // num_stages[0]: even filter stages; + // num_stages[1]: odd filter stages + int32_t iir_coeffs[MSIIR_MAX_COEFFS_PER_BAND]; // store order: 3-stage even, 2-stage odd. +} iir_mbdrc_iir_config_struct_t; + +// param id for get/set crossover freqs +#define IIR_MBDRC_PARAM_CROSSOVER_FREQS (10) // read/write +typedef struct iir_mbdrc_crossover_freqs_t +{ + uint32_t ch_idx; // channel index + uint32_t *crossover_freqs; // cross-over frequencies; +} iir_mbdrc_crossover_freqs_t; + // read/write num_band-1 crossover freqs + +// param id for reset +#define IIR_MBDRC_PARAM_SET_RESET (11) // write only +// clear below internal mem +// - Even IIR +// - Odd IIR +// - delay IIR +// - DRC +// - limiter + +#define IIR_MBDRC_PARAM_PER_CHANNEL_CALIBRATION_MODE (12) // read/write +typedef enum iir_mbdrc_per_channel_calib_mode_t +{ + IIR_MBDRC_PER_CHANNEL_CALIB_DISABLE = 0, // disable per-channel calibration(== old version) + IIR_MBDRC_PER_CHANNEL_CALIB_ENABLE // enable per-channel calibration +}iir_mbdrc_per_channel_calib_mode_t; + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif // IIR_MBDRC_CALIBRATION_API diff --git a/modules/processing/gain_control/iir_mbdrc/inc/iir_mbdrc_function_defines.h b/modules/processing/gain_control/iir_mbdrc/inc/iir_mbdrc_function_defines.h new file mode 100644 index 0000000..0c850bd --- /dev/null +++ b/modules/processing/gain_control/iir_mbdrc/inc/iir_mbdrc_function_defines.h @@ -0,0 +1,16 @@ +/* ========================================================================= + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause + ========================================================================== */ + +#ifdef __qdsp6__ + +#define IIR_MBDRC_SPLITFILTER32_LOOP_ASM +#define IIRDF1_32_1STORDER_ASM + +#endif + + + + + diff --git a/modules/processing/gain_control/limiter/api/api_limiter.h b/modules/processing/gain_control/limiter/api/api_limiter.h new file mode 100644 index 0000000..86c0f79 --- /dev/null +++ b/modules/processing/gain_control/limiter/api/api_limiter.h @@ -0,0 +1,160 @@ +#ifndef API_LIMITER_H +#define API_LIMITER_H +/*========================================================================*/ +/** +@file api_limiter.h + +@brief api_limiter.h: This file contains the Module Id, Param IDs and configuration + structures exposed by the Limiter Module. +*/ +/* ========================================================================= + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause + ========================================================================== */ + +/*------------------------------------------------------------------------ + * Include files + * -----------------------------------------------------------------------*/ +#include "ar_defs.h" + + +/** @ingroup ar_spf_mod_limiter_mod + Identifier for the parameter used by the Limiter module to configure the + tuning parameters of the limiter. + + @msgpayload + param_id_limiter_cfg_t + */ +#define PARAM_ID_LIMITER_CFG 0x08001050 + +/*# @h2xmlp_parameter {"PARAM_ID_LIMITER_CFG", PARAM_ID_LIMITER_CFG} + @h2xmlp_description {ID for the parameter used by the Limiter module to + configure the tuning parameters of the limiter.} */ + +/** @ingroup ar_spf_mod_limiter_mod + Payload of #PARAM_ID_LIMITER_CFG, which configures the output bit width, + number of channels, and sampling rate of the Limiter module. */ +#include "spf_begin_pack.h" +struct param_id_limiter_cfg_t +{ + uint32_t threshold; + /**< Threshold in decibels for the limiter output. + + @valuesbul + - For a 16-bit use case, the limiter threshold is [-96 dB..0 dB]. + - For a 24-bit use case, the limiter threshold is [-162 dB..24 dB]. + - For a true 32-bit use case, the limiter threshold is [-162 dB..0 dB]. + + If a value out of this range is configured, it is automatically limited + to the upper bound or low bound in the DSP processing. */ + + /*#< @h2xmle_description {Threshold in decibels for the limiter output. + For a 16-bit use case, the limiter threshold is + [-96 dB..0 dB]. \n + For a 24-bit use case, the limiter threshold is + [-162 dB..24 dB]. \n + For a true 32-bit use case, the limiter threshold + is [-162 dB..0 dB]. \n + If a value out of this range is configured, it is + automatically limited to the upper bound or low + bound in the DSP processing.} + @h2xmle_default {133909036} + @h2xmle_range {0..2127207634} + @h2xmle_dataFormat {Q27} */ + + uint32_t makeup_gain; + /**< Makeup gain in decibels for the limiter output. + + @values 1 through 32228 decibels (Default = 256) */ + + /*#< @h2xmle_description {Makeup gain in decibels for the limiter output.} + @h2xmle_default {256} + @h2xmle_range {1..32228} + @h2xmle_dataFormat {Q8} */ + + uint32_t gc; + /**< Coefficient for the limiter gain recovery. + + @values 0..32767 (Default = 32440) */ + + /*#< @h2xmle_description {Coefficient for the limiter gain recovery.} + @h2xmle_range {0..32767} + @h2xmle_default {32440} + @h2xmle_dataFormat {Q15} */ + + uint32_t max_wait; + /**< Maximum limiter waiting time in seconds. + + @values 0 through 327 (Default = 33) */ + + /*#< @h2xmle_description {Maximum limiter waiting time in seconds.} + @h2xmle_range {0..327} + @h2xmle_default {33} + @h2xmle_dataFormat {Q15} */ + + uint32_t gain_attack; + /**< Limiter gain attack time in seconds (Q31 format). */ + + /*#< @h2xmle_description {Limiter gain attack time.} + @h2xmle_default {188099712} + @h2xmle_range {0..2147483648} + @h2xmle_dataFormat {Q31} */ + + uint32_t gain_release; + /**< Limiter gain release time in seconds (Q31 format) */ + + /*#< @h2xmle_description {Limiter gain release time.} + @h2xmle_default {32559488} + @h2xmle_range {0..2147483648} + @h2xmle_dataFormat {Q31} */ + + uint32_t attack_coef; + /**< Speed coefficient for the limiter gain attack time. */ + + /*#< @h2xmle_description {Speed coefficient for the limiter gain attack + time.} + @h2xmle_default {32768} + @h2xmle_range {32768..3276800} + @h2xmle_dataFormat {Q15} */ + + uint32_t release_coef; + /**< Speed coefficient for the limiter gain release time. */ + + /*#< @h2xmle_description {Speed coefficient for the limiter gain release + time.} + @h2xmle_default {32768} + @h2xmle_range {32768..3276800} + @h2xmle_dataFormat {Q15} */ + + uint32_t hard_threshold; + /*#< Hard threshold in decibels for the limiter output. + + - For a 16-bit use case, the limiter hard threshold is [-96 dB..0 dB]. + - For a 24-bit use case, the limiter hard threshold is + [-162 dB..24 dB]. + - For a true 32-bit use case, the limiter hard threshold is + [-162 dB..0 dB]. + + If a value out of this range is configured, it is automatically limited + to the upper bound or low bound in the DSP processing. */ + + /*#< @h2xmle_description {Hard threshold in decibels for the limiter output. + For a 16-bit use case, the limiter hard threshold + is [-96 dB..0 dB]. \n + For a 24-bit use case, the limiter hard threshold + is [-162 dB..24 dB]. \n + For a true 32-bit use case, the limiter hard + threshold is [-162 dB..0 dB]. \n + If a value out of this range is configured, it + is automatically limited to the upper bound or low + bound in the DSP processing.} + @h2xmle_default {134217728} + @h2xmle_range {0..2127207634} + @h2xmle_dataFormat {Q27} */ +} +#include "spf_end_pack.h" +; +typedef struct param_id_limiter_cfg_t param_id_limiter_cfg_t; + + +#endif /* API_LIMITER_H */ diff --git a/modules/processing/gain_control/limiter/build/CMakeLists.txt b/modules/processing/gain_control/limiter/build/CMakeLists.txt new file mode 100644 index 0000000..0d8f6c3 --- /dev/null +++ b/modules/processing/gain_control/limiter/build/CMakeLists.txt @@ -0,0 +1,21 @@ +#[[ + @file CMakeLists.txt + + @brief + + @copyright + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause +]] +cmake_minimum_required(VERSION 3.10) + +#Include directories +include_directories( + ${LIB_ROOT}/api + ../lib/inc + ../../../../../fwk/spf/interfaces/module/shared_lib_api/inc/generic + ../../../../cmn/common/utils/inc + ) + +#Add the sub directories +add_subdirectory(../lib/build lib) diff --git a/modules/processing/gain_control/limiter/lib/build/CMakeLists.txt b/modules/processing/gain_control/limiter/lib/build/CMakeLists.txt new file mode 100644 index 0000000..796ac31 --- /dev/null +++ b/modules/processing/gain_control/limiter/lib/build/CMakeLists.txt @@ -0,0 +1,25 @@ +#[[ + @file CMakeLists.txt + + @brief + + @copyright + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause +]] +cmake_minimum_required(VERSION 3.10) + +#Add the source files +set (lib_srcs_list + ${LIB_ROOT}/src/limiter.c + ${LIB_ROOT}/src/limiter24.c + ) + +#Call spf_build_static_library to generate the static library +spf_build_static_library(CLimiter24Lib + "${lib_incs_list}" + "${lib_srcs_list}" + "${lib_defs_list}" + "${lib_flgs_list}" + "${lib_link_libs_list}" + ) diff --git a/modules/processing/gain_control/limiter/lib/inc/limiter24_api.h b/modules/processing/gain_control/limiter/lib/inc/limiter24_api.h new file mode 100644 index 0000000..889126d --- /dev/null +++ b/modules/processing/gain_control/limiter/lib/inc/limiter24_api.h @@ -0,0 +1,250 @@ +/*======================= COPYRIGHT NOTICE ==================================*] +Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. +SPDX-License-Identifier: BSD-3-Clause +[*===========================================================================*] +[*****************************************************************************] +[* FILE NAME: limiter24_api.h TYPE: C-header file *] +[* DESCRIPTION: Public header file for the 24bit Limiter algorithm. *] +[*****************************************************************************/ + +#ifndef _LIMITER_API_H +#define _LIMITER_API_H + +#if ((defined __hexagon__) || (defined __qdsp6__)) +#define LIM_ASM +#endif +/*---------------------------------------------------------------------------- +* Include Files +* -------------------------------------------------------------------------*/ + +#include "posal.h" + +#define LIMITER_BLOCKSIZE 480 /* Maximum frame Size */ +#define LIM_MAX_NUM_CHANNELS 2 +#define MAX_LIM_DELAY_BUF_SIZE 48*10 //10ms delay at 48KHz +#define LIM_MAX_DELAY 327 //10ms = 0.01 in Q15 format + +/*---------------------------------------------------------------------------- +* Type Declarations for overall configuration and state data structures +* -------------------------------------------------------------------------*/ +#ifdef __cplusplus +extern "C" +{ +#endif +/** +@brief Algorithm Tuning parameters to be read from the parameter file +*/ +typedef enum +{ + LIM_ENABLE = 0, // Q0 Enable word for the limiter + LIM_MODE = 1, // Q0 Mode word for the limiter + LIM_THRESH = 2, // Threshold value: Q3.15 for 16bit; Q8.23 for 32bit + LIM_MAKEUP_GAIN = 3, // Makeup gain: Q7.8 + LIM_GC = 4, // Q15 Gain recovery coefficient + LIM_DELAY = 5, // Q15 Delay in seconds + LIM_MAX_WAIT = 6, // Q15 Maximum waiting time in seconds for delay-less limiter + LIM_PAR_TOTAL = 7 // Total number of configuration parameters +} LimParamsList; + +typedef enum +{ + FADE_INIT = 0, + FADE_START, + FADE_STOP + +} FadeConstants; +/** +@brief Default values of tuning parameters and hardcoded parameters used in the algorithm +*/ +typedef enum +{ + // Default parameters + LIM_DISABLE_VAL = 0x0000, // Q0 Disable word for the limiter + LIM_ENABLE_VAL = 0x0001, // Q0 Enable word for the limiter + LIM_MODE_VAL = 0x0001, // Q0 Default mode word for the limiter + LIM_THRESH_VAL32 = 93945856, // Q8.27 (0.7) Default threshold: -3 dB + LIM_THRESH_VAL16 = 22936, // Q3.15 (0.7) Default threshold: -3 dB + LIM_MAKEUP_GAIN_VAL = 0x0100, // Q7.8 (1) Default gain: 0 dB + LIM_GC_VAL = 32440, // Q15 (0.99) Default gain recovery coefficient + LIM_DELAY_VAL = 82, // Q15 (0.0025) Default delay in seconds + LIM_MAX_WAIT_VAL = 82 // Q15 (0.0025) Default waiting time in seconds +} LimParamsDefault; + +/** +@brief configuration parameter structure per channel +*/ +typedef struct _LimCfgParams +{ + int32 limThresh; // Threshold value: Q3.15 for 16bit; Q8.23 for 32bit + int32 limEnable; // Q0 Enable word for the limiter feature + int32 limMode; // Q0 Mode word for the limiter: 1 enable limiter for the ba + int32 limMakeUpGain; // Q7.8 Makeup gain + int32 limGc; // Q15 Gain Recovery coefficient + int16 limDelay; // Q15 Limiter delay in seconds + int16 limMaxWait; // Q15 Limiter waiting time in seconds + +} LimCfgParams; + +/** +@brief limiter configuration structure +*/ +typedef struct _LimCfgType +{ + // Don't change the order of the first 5 fields which are used in ASM + int32 threshL32Q15; + int32 mGainL32; // Left-shifted make-up gain in L32 + int32 limDelayMinusOne; // Q0 Limiter delay in samples minus one sample + int32 limGc; // Q15 Gain Recovery coefficient + int32 limWaitMinusOne; // Q0 Limiter Max wait in samples minus one sample + + LimCfgParams params; /* Configuration struct for limiter parameters */ + +} LimCfgType; + + +/** +@brief limiter processing data structure +*/ +typedef struct _LimDataType +{ // Don't change the order of the first 8 fields which are used in ASM + int32 *pInpBuffer; /* pointer to delay buffer array */ + int32 *pZcBuffer; /* pointer zero-crossing buffer array */ + int32 localMaxPeakL32; /* Local Maximum peak array*/ + int32 prevSampleL32; /* Previous sample in the buffer */ + int32 curIndex; /* Current sample index */ + int32 prev0xIndex; /* Previous zero-crossing index array*/ + int32 gainVarQ15; /* Q15 Limiter gain variable array */ + int32 gainQ15; /* Q15 Limiter gain array */ + int32 *ptempBuffer; /* tempory buffer array*/ + int32 fadeFlag; /* Q0 Fade flag for transition purposes */ + int32 dummy; // for 8-byte alignment of the structure +} LimDataType; + + +typedef struct _CLimiterLib +{ + LimCfgType LimCfgStruct __attribute__ ((aligned (8))); + LimDataType LimDataStruct __attribute__ ((aligned (8))); + int32 *pDelayBuf; + int32 *pZcBuf; + int32 bitsPerSample; + void (*lim_proc) (LimCfgType *pCfg,LimDataType *pData, int32 *in, void *out,int16 iSubFrameSize); + void (*apply_makeup_gain)(LimCfgType *pCfg,LimDataType *pData,void** pOut,int16 iSubFrameSize); + +}CLimiterLib; + +/** +@brief Initialize LIM algorithm with default value + +Performs initialization of data structures for the +LIM algorithm. A pointer to static memory is storage +is passed for configuring the LIM static configuration +structure. + +@param params: [in] pointer to configuration structure +@param numChannels: [in] num of channels +@param bitsPerSample: [in] bits per sample +*/ +int Lim_init_default(int32 *params, + int16 numChannels, + int16 bitsPerSample); + +/** +@brief Initialize LIM algorithm + +Performs initialization of data structures for the +LIM algorithm. A pointer to static memory is storage +is passed for configuring the LIM static configuration +structure. + +@param pLimStruct: [in] pointer to limiter library struct +@param params: [in] pointer to tuning params list +@param chIdx: [in] current channel index +@param sampleRate: [in] Input sampling rate +@param bitsPerSample: [in] bits per sample +*/ +int Lim_init(CLimiterLib *pLimStruct, + int32 *params, + int16 chIdx, + int32 sampleRate, + int32 bitsPerSample); + +/** +@brief Re_Initialize LIM algorithm +A pointer to static memory storage is passed for +configuring the LIM static configuration structure. + +@param pLimCfgStruct: [in] pointer to configuration structure +@param params: [in] Pointer to tuning parameter list +@param sampleRate: [in] Input sampling rate +*/ + +int Lim_reinit(LimCfgType *pLimCfgStruct, + int32 *params, + int32 sampleRate); + +/** +@brief Reset LIM data structure + +Performs reset of data structures for the +LIM algorithm. A pointer to static memory in storage +is passed for resetting the LIM static configuration +structure. + +@param pLimDataStruct: [in] pointer to data structure +@param pLimCfgStruct: [in] pointer to limiter cfg struct +*/ +void Lim_DataStruct_Reset(LimDataType *pLimDataStruct, + LimCfgType *pLimCfgStruct); + +/** +@brief Process input audio data with limiter algorithm + +@param pLimStruct: [in] pointer to limiter library structure +@param pOut: [out] Pointer to 16-bit Q15 output channel data +@param pInp: [in] Pointer to 32-bit Q15 input channel data +@param frameSize: [in] Input frame size +@param bypass: [in] bypass flag +*/ +void Lim_process(CLimiterLib *pLimStruct, + void *pOut, + int32 *pInp, + int16 frameSize, + int16 bypass); + +/** +@brief Returns the limiter library size for a given delay,samplerata,numchannels + so that the library users can use this funtion to allocate the num of bytes + returned by this. +@param limDelay: [in] limiter delay in sec Q15 format +@param sampleRate: [in] sampleRate +@param numChannles: [in] num of channels +@param limLibSize: [out] total library size for the above input params +*/ + +int32 Lim_get_lib_size(int16 limDelay, + int32 sampleRate, + int32 numChannels); + + +/** +@brief Places the library structures and buffers at the given input pointer/memory location +@param pLimBufPtr: [in] the start address of the malloced memory +@param pLimiter: [in] address of the first element of the array of limiter pointers, + the array members will be filled with the input memory pointer +@param limDelay: [in] limiter delay in sec Q15 format +@param sampleRate: [in] sampleRate +@param chIdx: [in] channel index num +*/ + +void Lim_set_lib_memory(int8 *pLimBufPtr, + CLimiterLib **pLimiter, + int16 limDelay, + int32 sampleRate, + int32 chIdx); + +#ifdef __cplusplus +} +#endif + +#endif /* #ifndef _LIMITER_API_H */ diff --git a/modules/processing/gain_control/limiter/lib/inc/limiter_api.h b/modules/processing/gain_control/limiter/lib/inc/limiter_api.h new file mode 100644 index 0000000..46d99ae --- /dev/null +++ b/modules/processing/gain_control/limiter/lib/inc/limiter_api.h @@ -0,0 +1,125 @@ +#ifndef LIMITER_API_H +#define LIMITER_API_H +/*============================================================================ + @file limiter_api.h + + Public API for Low Distortion Limiter, including zero-crossing algorithm + and peak history (lower distortion) algorithm + +Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. +SPDX-License-Identifier: BSD-3-Clause +============================================================================*/ +#include "limiter_calibration_api.h" + +#if ((defined __hexagon__) || (defined __qdsp6__)) +#define LIM_ASM +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/*---------------------------------------------------------------------------- + Library Version +----------------------------------------------------------------------------*/ +#define LIMITER_LIB_VER (0x01000202) // lib version : 1.2.2.a1 + // (major.minor.bug) (8.16.8 bits) + +/*---------------------------------------------------------------------------- + Error code +----------------------------------------------------------------------------*/ +typedef enum LIMITER_RESULT { + LIMITER_SUCCESS = 0, + LIMITER_FAILURE, + LIMITER_MEMERROR +} LIMITER_RESULT; + +/*---------------------------------------------------------------------------- + Type Declarations +----------------------------------------------------------------------------*/ +typedef struct limiter_static_vars_t { // ** static params + int32 data_width; // 16 (Q15) or 32 (Q27) + int32 sample_rate; // sampling rate, range: all audio sampling rate + int32 max_block_size; // max block size, range: any block size + int32 num_chs; // num of channels + int32 delay; // limiter delay (Q15 value of sec), range: 0 ~ 10 miliseconds +} limiter_static_vars_t; + +/*---------------------------------------------------------------------------- + Type Declarations for new limiter v2 +----------------------------------------------------------------------------*/ +typedef struct limiter_static_vars_v2_t { // ** static params + int32 data_width; // 16 (Q15) or 32 (Q27 or Q31) + int32 sample_rate; // sampling rate, range: all audio sampling rate + int32 max_block_size; // max block size, range: any block size + int32 num_chs; // num of channels + int32 delay; // limiter delay (Q15 value of sec), range: 0 ~ 10 miliseconds + int32 q_factor; // Q factor of input/output data + int32 history_winlen; // length of history window (Q15 value of sec), range: 0 ~ 40 miliseconds + // 0 - zero-crossing algo; > 0 - peak history algo +} limiter_static_vars_v2_t; + +typedef struct limiter_mem_req_t { // ** memory requirements + uint32 mem_size; // lib mem size (lib_mem_ptr ->) + uint32 stack_size; // stack mem size +} limiter_mem_req_t; + +typedef struct limiter_lib_t { // ** library struct + void* mem_ptr; // mem pointer +} limiter_lib_t; + +/*----------------------------------------------------------------------------- + Function Declarations +-----------------------------------------------------------------------------*/ + +// ** Process one block of samples (multi channel) +// lib_ptr: [in] pointer to library structure +// out_ptr: [out] pointer to output sample block (multi channel double ptr) +// in_ptr: [in] pointer to input sample block (multi channel double ptr) +// samples: [in] number of samples to be processed per channel +LIMITER_RESULT limiter_process(limiter_lib_t *lib_ptr, void **out_ptr, int32 **in_ptr, uint32 samples); + +// ** Get library memory requirements +// mem_req_ptr: [out] pointer to mem requirements structure +// limiter_static_vars_t: [in] pointer to static variable structure +LIMITER_RESULT limiter_get_mem_req(limiter_mem_req_t *mem_req_ptr, limiter_static_vars_t* static_vars_ptr); + +// ** Get library memory requirements for new limiter v2 +// mem_req_ptr: [out] pointer to mem requirements structure +// limiter_static_vars_v2_t: [in] pointer to static variable structure v2 +LIMITER_RESULT limiter_get_mem_req_v2(limiter_mem_req_t *mem_req_ptr, limiter_static_vars_v2_t* static_vars_v2_ptr); + +// ** Partition, initialize memory, and set default values +// lib_ptr: [in, out] pointer to library structure +// static_vars_ptr: [in] pointer to static variable structure +// mem_ptr: [in] pointer to allocated memory +// mem_size: [in] size of memory allocated +LIMITER_RESULT limiter_init_mem(limiter_lib_t *lib_ptr, limiter_static_vars_t* static_vars_ptr, void* mem_ptr, uint32 mem_size); + +// ** Partition, initialize memory, and set default values +// lib_ptr: [in, out] pointer to library structure +// static_vars_v2_ptr: [in] pointer to static variable structure v2 +// mem_ptr: [in] pointer to allocated memory +// mem_size: [in] size of memory allocated +LIMITER_RESULT limiter_init_mem_v2(limiter_lib_t *lib_ptr, limiter_static_vars_v2_t* static_vars_v2_ptr, void* mem_ptr, uint32 mem_size); + +// ** Set parameters to library +// lib_ptr: [in, out] pointer to lib structure +// param_id: [in] parameter id +// param_ptr: [in] pointer to the memory where the new values are stored +// param_size:[in] size of the memory pointed by param_ptr +LIMITER_RESULT limiter_set_param(limiter_lib_t* lib_ptr, uint32 param_id, void* param_ptr, uint32 param_size); + +// ** Get parameters from library +// lib_ptr: [in] pointer to library structure +// param_id: [in] parameter id +// param_ptr: [out] pointer to the memory where the retrieved value is going to be stored +// param_size:[in] size of the memory pointed by param_ptr +// param_actual_size_ptr: [out] pointer to memory that will hold the actual size of the parameter +LIMITER_RESULT limiter_get_param(limiter_lib_t* lib_ptr, uint32 param_id, void* param_ptr, uint32 param_size, uint32 *param_actual_size_ptr); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* LIMITER_API_H */ diff --git a/modules/processing/gain_control/limiter/lib/inc/limiter_calibration_api.h b/modules/processing/gain_control/limiter/lib/inc/limiter_calibration_api.h new file mode 100644 index 0000000..db82186 --- /dev/null +++ b/modules/processing/gain_control/limiter/lib/inc/limiter_calibration_api.h @@ -0,0 +1,95 @@ +#ifndef LIMITER_CALIBRATION_API_H +#define LIMITER_CALIBRATION_API_H +/*============================================================================ + @file limiter_calibration_api.h + + Calibration (Public) API for Low Distortion Limiter, including zero-crossing algorithm + and peak history (lower distortion) algorithm + +Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. +SPDX-License-Identifier: BSD-3-Clause +============================================================================*/ +#include "AudioComdef.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/*---------------------------------------------------------------------------- + Parameters with IDs +----------------------------------------------------------------------------*/ +#define LIMITER_PARAM_LIB_VER (0) // ** param: library version +typedef int32 limiter_version_t; // access: get only + +#define LIMITER_PARAM_MODE (1) // ** param: processing mode +typedef enum limiter_mode_t { // access: get & set + MAKEUPGAIN_ONLY = 0, // 0: apply makeup gain, no delay + NORMAL_PROC, // 1: normal processing +} limiter_mode_t; + +#define LIMITER_PARAM_BYPASS (2) // ** param: bypass +typedef int32 limiter_bypass_t; // access: get & set + // range: [1: bypass 0: normal] + +#define LIMITER_PARAM_TUNING (3) // ** param: per channel tuning param + // access: get & set +typedef struct limiter_tuning_t { // note: runtime ok, no mem flush + int32 ch_idx; // channel index for this set + int32 threshold; // threshold (16bit: Q15; 32bit:Q27) + // range: 16bit - 0 ~ -96 dB; 24bit - 24 ~ -120dB; 32bit - 0 ~ -120dB + // default: corresponds to -3 dB value + int32 makeup_gain; // make up gain (Q8) + // range: -48 ~ 42 dB + // default: 0 dB + int32 gc; // recovery const (Q15) + // range: 0 ~ 1 + // default: 0.99 + int32 max_wait; // max wait (Q15 value of sec) + // range: 0 ~ 10 miliseconds + // default: 2ms +} limiter_tuning_t; + +#define LIMITER_PARAM_RESET (4) // ** param: reset internal memories + // access: set only + +#define LIMITER_PARAM_TUNING_V2 (5) // ** param: per channel tuning param + // access: get & set +typedef struct limiter_tuning_v2_t { // note: runtime ok, no mem flush + int32 ch_idx; // channel index for this set + int32 threshold; // threshold (16bit: Q15; 32bit:Q27) + // range: 16bit - 0 ~ -96 dB; 24bit - 24 ~ -120dB; 32bit - 0 ~ -120dB + // default: corresponds to -3 dB value + int32 makeup_gain; // make up gain (Q8) + // range: -48 ~ 42 dB + // default: 0 dB + int32 gc; // recovery const (Q15) + // range: 0 ~ 1 + // default: 0.99 + int32 max_wait; // max wait (Q15 value of sec) + // range: 0 ~ 10 miliseconds + // default: 2ms + + uint32 gain_attack; // limiter gain attack time const (Q31) + // range: corresponds to 0 ~ 5 seconds attack time + // default: corresponds to 0.5 ms attack time + uint32 gain_release; // limiter gain release time const(Q31) + // range: corresponds to 0 ~ 5 seconds release time + // default: corresponds to 3 ms release time + uint32 attack_coef; // limiter gain attack time speed coef (Q15) + // range: 1 ~ 100 + // default: 1 + uint32 release_coef; // limiter gain release time speed coef (Q15) + // range: 1 ~ 100 + // default: 1 + int32 hard_threshold; // threshold for hard limiter (16bit: Q15; 32bit:Q27) + // range: 16bit - 0 ~ -96 dB; 24bit - 24 ~ -120dB; 32bit - 0 ~ -120dB + // default: corresponds to -3 dB value +} limiter_tuning_v2_t; + + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* LIMITER_CALIBRATION_API_H */ diff --git a/modules/processing/gain_control/limiter/lib/src/limiter.c b/modules/processing/gain_control/limiter/lib/src/limiter.c new file mode 100644 index 0000000..40fd614 --- /dev/null +++ b/modules/processing/gain_control/limiter/lib/src/limiter.c @@ -0,0 +1,1461 @@ +/*============================================================================ + @file limiter.c + + Low distortion Limiter Implementation, including zero-crossing algorithm, + and peak-history algorithm + + + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause +============================================================================*/ + +/*---------------------------------------------------------------------------- + Include Files +----------------------------------------------------------------------------*/ +#include "limiter.h" +#include "audio_divide_qx.h" +#include "audio_dsp32.h" +#include "simple_mm.h" +#include "audio_basic_op_ext.h" +#include "stringl.h" + +#ifdef LIM_ASM +void process_delay_zc_asm(limiter_per_ch_t *per_ch_ptr, int32 *scratch32, int32 dly_smps_m1, int32 samples); +void process_delayless_zc_asm(limiter_per_ch_t *per_ch_ptr, int32 *scratch32, int32 samples); +void process_with_history_peak_asm(limiter_per_ch_t *per_ch_ptr, + int32 * scratch32, + int32 dly_smps_m1, + int32 samples, + int32 q_factor); +void apply_makeup_gain_asm(limiter_private_t *obj_ptr, void **out_ptr, int32 samples, uint32_t ch); +#endif // LIM_ASM + +/*---------------------------------------------------------------------------- + Local Functions +----------------------------------------------------------------------------*/ +int32 ms_to_sample_lim(int32 period, int32 sampling_rate) +{ + int32 samples; + + samples = s32_saturate_s64(s64_mult_s32_s32_shift(c_1_over_1000_q31, sampling_rate, 16)); // Q15 + samples = s32_saturate_s64(s64_mult_s32_s32_shift(samples, period, 17)); // Q0 + return samples; +} + +/* reset internal memories */ +static void reset(limiter_private_t *obj_ptr) +{ + int32 ch; + limiter_per_ch_t *per_ch_ptr; + int32 delay_samples = obj_ptr->dly_smps_m1 + 1; + + obj_ptr->bypass_smps = ms_to_sample_lim(BYPASS_TRANSITION_PERIOD_MSEC, obj_ptr->static_vars.sample_rate); + for (ch = 0; ch < obj_ptr->static_vars.num_chs; ++ch) + { + per_ch_ptr = &obj_ptr->per_ch[ch]; + + // init the de-compressed limiter gain buffer + per_ch_ptr->gain_q27 = c_gain_unity; // unity gain (Q27) + per_ch_ptr->target_gain_q27 = c_gain_unity; // unity gain (Q27) + + // init static members of the data structure + per_ch_ptr->cur_idx = 0; // init current operating index + per_ch_ptr->peak_subbuf_idx = 0; // init current peak buffer index + per_ch_ptr->prev_peak_idx = 0; // init the previous index + per_ch_ptr->local_max_peak = 0; // init the local maxima peak + per_ch_ptr->global_peak = 0; // init the global peak + per_ch_ptr->fade_flag = 0; // init fade flag + per_ch_ptr->prev_zc_idx = 0; + per_ch_ptr->prev_sample_l32 = 0; + per_ch_ptr->fade_flag_prev = 0; // init previous fade flag + + // clean buffer memories + buffer32_empty_v2(per_ch_ptr->delay_buf, delay_samples); + + if (obj_ptr->static_vars.history_winlen > 0) + { + buffer32_empty_v2(per_ch_ptr->history_peak_buf, c_local_peak_bufsize); + } + else + { + buffer32_empty_v2(per_ch_ptr->zc_buf, delay_samples); + } + } + + buffer32_empty_v2(obj_ptr->scratch_buf, obj_ptr->static_vars.max_block_size); +} + +/* convert tuning parameter into internal vars */ +static void convert_tuning_params(limiter_private_t *obj_ptr, int32 ch) +{ + int32 accu32; + int16 tmp16; + limiter_tuning_v2_t *tuning_ptr = &obj_ptr->per_ch[ch].tuning_params; + int32 q_factor = obj_ptr->static_vars.q_factor; + + // convert threshold to be aligned with input q-factor + int32 default_qfactor = (obj_ptr->static_vars.data_width == 16 ? 15 : 27); + + obj_ptr->per_ch[ch].shifted_threshold = + s32_shl_s32_sat(tuning_ptr->threshold, (int16)q_factor - (int16)default_qfactor); + obj_ptr->per_ch[ch].shifted_hard_threshold = + s32_shl_s32_sat(tuning_ptr->hard_threshold, (int16)q_factor - (int16)default_qfactor); + + // convert and calculatge max wait sample - value in static mem + if (obj_ptr->static_vars.history_winlen == 0) + { // process using zc, without history peak buffer + tmp16 = s16_saturate_s32(tuning_ptr->max_wait); + } + else + { // process with history peak buffer + // history subbuf length = (history window length)/4 + 1 + // peak buffer len = 4 + tmp16 = s16_saturate_s32(s32_add_s32_s32_sat(obj_ptr->static_vars.history_winlen, 0x0004) >> 2); + } + accu32 = s32_mult_s32_s16_rnd_sat(obj_ptr->static_vars.sample_rate, tmp16); + obj_ptr->per_ch[ch].max_wait_smps_m1 = s16_saturate_s32(accu32) - 1; + + // left shift the make up gain by (7+1) to Q7.16 before multiplication with the data + obj_ptr->per_ch[ch].makeup_gain_q16 = s32_shl_s32_sat(tuning_ptr->makeup_gain, 8); +} + +/* set default values for limiter */ +static void set_default(limiter_private_t *obj_ptr) +{ + int16 ch; + limiter_tuning_v2_t *tuning_ptr; + int32 q_factor = obj_ptr->static_vars.q_factor; + int32 default_threshold_32 = 93945856; + int32 default_threshold_16 = 22936; + + for (ch = 0; ch < obj_ptr->static_vars.num_chs; ++ch) + { + tuning_ptr = &obj_ptr->per_ch[ch].tuning_params; + + tuning_ptr->ch_idx = ch; + if (32 == obj_ptr->static_vars.data_width) + { + // threshold: -3 dB (Q27, 0.7) + tuning_ptr->threshold = s32_shl_s32_sat(default_threshold_32, (int16)q_factor - 27); + // threshold: -3 dB (Q27, 0.7) + tuning_ptr->hard_threshold = s32_shl_s32_sat(default_threshold_32, (int16)q_factor - 27); + } + else + { // 16 + // threshold: -3 dB (Q15, 0.7) + tuning_ptr->threshold = s32_shl_s32_sat(default_threshold_16, (int16)q_factor - 15); + // threshold: -3 dB (Q15, 0.7) + tuning_ptr->hard_threshold = s32_shl_s32_sat(default_threshold_16, (int16)q_factor - 15); + } + + tuning_ptr->makeup_gain = 256; // unity (Q8) + tuning_ptr->gc = 32440; // gain recovery coeff (Q15, 0.99) + tuning_ptr->max_wait = 82; // max wait (Q15 sec, 0.0025) + + tuning_ptr->gain_attack = 188099735; // gain attack time constant (Q31) + tuning_ptr->gain_release = 32559427; // gain release time constant (Q31) + tuning_ptr->attack_coef = 32768; // unity attack time constant coef (Q15) + tuning_ptr->release_coef = 32768; // unity release time constant coef (Q15) + + convert_tuning_params(obj_ptr, ch); + } + + // default to normal processing mode and no bypass + obj_ptr->mode = NORMAL_PROC; + obj_ptr->bypass = 0; + + // flush memory + reset(obj_ptr); +} + +int32 search_global_peak(int32 *history_peak_buf, int32 num_history_peaks) +{ + int32 global_peak = history_peak_buf[0]; + int32 i; + + for (i = 1; i < num_history_peaks; i++) + { + if (global_peak < history_peak_buf[i]) + { + global_peak = history_peak_buf[i]; + } + } + return global_peak; +} + +#ifndef LIM_ASM +/*---------------------------------------------------------------------------- +Apply gain smoothing logic to smooth the gain, so that the gain will achieve +the target gain within pre-defined time constant. + +Use a look ahead buffer (limiter delay) to detect the ¡®local peak¡¯, so that +the gain can be gradually adapted to the target value before the local peak. + +Introduce a long peak window to track the ¡®global peak¡¯ during certain period, +and then decide the target gain using this global peak. In this way, the target +gain change/fluctuation will be less. This helps to achieve a smoother gain and +less distortion. +----------------------------------------------------------------------------*/ +static void process_with_history_peak(limiter_per_ch_t *per_ch_ptr, + int32 * scratch32, + int32 dly_smps_m1, + int32 samples, + int32 q_factor) +{ + int32 j, gp_change_flag = 0; + int64 accu64; + int32 inpL32, attn32, absL32, iq32 /*, prod32*/; + int32 cur_idx, peak_subbuf_idx, prev_peak_idx, max_wait_smps_m1; + int32 global_peak, local_max_peak, new_global_peak; + int32 threshold, hard_thresh, gain_diff_q27; + uint32 time_const, time_coef; + int32 target_gain_q27, gain_q27; + + threshold = per_ch_ptr->shifted_threshold; + hard_thresh = per_ch_ptr->shifted_hard_threshold; + + max_wait_smps_m1 = per_ch_ptr->max_wait_smps_m1; + + // Index for current sample in the zero-crossing buffer/input buffer + cur_idx = per_ch_ptr->cur_idx; + peak_subbuf_idx = per_ch_ptr->peak_subbuf_idx; + prev_peak_idx = per_ch_ptr->prev_peak_idx; + local_max_peak = per_ch_ptr->local_max_peak; + global_peak = per_ch_ptr->global_peak; + target_gain_q27 = per_ch_ptr->target_gain_q27; + gain_q27 = per_ch_ptr->gain_q27; + + for (j = 0; j < samples; ++j) + { + + // Extract and store the current input data + inpL32 = scratch32[j]; + + // Compute the absolute magnitude of the input + absL32 = (int32)u32_abs_s32_sat(inpL32); + + // Compute the local maxima in the input audio + local_max_peak = s32_max_s32_s32(local_max_peak, absL32); + + /************************************************************************ + Compute the global maxima - start + *************************************************************************/ + gp_change_flag = 0; + if (global_peak < local_max_peak) + { + global_peak = local_max_peak; + gp_change_flag = 1; + } + + peak_subbuf_idx++; + + // If the 'max_wait_smps_m1' samples' local maxima is computed, store the + // value into the peak history buffer, and re-compute the global peak + if (peak_subbuf_idx > max_wait_smps_m1) + { + per_ch_ptr->history_peak_buf[prev_peak_idx] = local_max_peak; + prev_peak_idx = s32_modwrap_s32_u32(prev_peak_idx + 1, c_local_peak_bufsize); + local_max_peak = 0; + peak_subbuf_idx = 0; + + new_global_peak = search_global_peak(per_ch_ptr->history_peak_buf, c_local_peak_bufsize); + if (global_peak != new_global_peak) + { + global_peak = new_global_peak; + gp_change_flag = 1; + } + } + + // When the user wrongly set history window length < delay, this case will happen + // Set global peak = abs(current sample) to avoid overshoot + if ((int32)u32_abs_s32_sat(per_ch_ptr->delay_buf[cur_idx]) > global_peak) + { + global_peak = (int32)u32_abs_s32_sat(per_ch_ptr->delay_buf[cur_idx]); + gp_change_flag = 1; + } + + if (gp_change_flag == 1) + { + if (global_peak > threshold) + { + // Use Q6 DSP's linear approximation division routine to lower down the MIPS + // The inverse is computed with a normalized shift factor + accu64 = dsplib_approx_divide(threshold, global_peak); + attn32 = (int32)accu64; // Extract the normalized inverse + iq32 = (int32)(accu64 >> 32); // Extract the normalization shift factor + + // Shift the result to get the quotient in desired Q27 format + target_gain_q27 = s32_shl_s32_sat(attn32, (int16)iq32 + 27); + } + else + { + target_gain_q27 = c_gain_unity; + } + } + + /************************************************************************ + Implementation of Limiter gain computation + *************************************************************************/ + if (gain_q27 != target_gain_q27) + { // do gain smoothing + if (gain_q27 < target_gain_q27) + { + time_coef = per_ch_ptr->tuning_params.release_coef; + time_const = per_ch_ptr->tuning_params.gain_release; + } + else + { + time_coef = per_ch_ptr->tuning_params.attack_coef; + time_const = per_ch_ptr->tuning_params.gain_attack; + } + + // accu64 = (1-coef)*abs(x) + coef + accu64 = s64_mult_s32_s32_shift(s32_sub_s32_s32_sat(c_unity_q15, (int32)time_coef), + absL32, + 32 - (int16)q_factor); // Q15 + accu64 = s64_add_s32_s32(s32_saturate_s64(accu64), (int32)time_coef); // Q15 + + // time_const = accu64 * gain_release, Q31 + time_const = (uint32)s64_mult_s32_u32_shift(s32_saturate_s64(accu64), time_const, 17); + + // limit the time_const uppper bound to be 1 + time_const = time_const > c_unity_q31 ? c_unity_q31 : time_const; + + // gain_q27 = gain_q27*(1-time_const) + target_gain_q27*time_const + // = gain_q27 + (target_gain_q27 - gain_q27)*time_const + gain_diff_q27 = s32_sub_s32_s32_sat(target_gain_q27, gain_q27); + gain_q27 = + s32_add_s32_s32_sat(gain_q27, s32_saturate_s64(s64_mult_s32_u32_shift(gain_diff_q27, time_const, 1))); + } + + /************************************************************************ + Implementation of Limiter gain on the input data + *************************************************************************/ + // Gain application - Multiply and shift and round and sat (one cycle in Q6) + if (dly_smps_m1 >= 0) + { // if process with limiter delay + scratch32[j] = s32_saturate_s64( + s64_shl_s64(s64_add_s64_s64(s64_mult_s32_s32(per_ch_ptr->delay_buf[cur_idx], gain_q27), 0x4000000), -27)); + // Store the new input sample in the input buffer + per_ch_ptr->delay_buf[cur_idx] = inpL32; + + cur_idx = s32_modwrap_s32_u32(cur_idx + 1, dly_smps_m1 + 1); + } + else + { // if process delayless + scratch32[j] = + s32_saturate_s64(s64_shl_s64(s64_add_s64_s64(s64_mult_s32_s32(inpL32, gain_q27), 0x4000000), -27)); + } + + /************************************************************************ + Apply hard-limiting if output exceeds hard threshold + *************************************************************************/ + if (scratch32[j] > hard_thresh || scratch32[j] < -hard_thresh) + { + scratch32[j] = scratch32[j] > 0 ? hard_thresh : -hard_thresh; + gain_q27 = target_gain_q27; + } + + } /* for loop */ + + per_ch_ptr->cur_idx = cur_idx; + per_ch_ptr->prev_peak_idx = prev_peak_idx; + per_ch_ptr->peak_subbuf_idx = peak_subbuf_idx; + per_ch_ptr->local_max_peak = local_max_peak; + per_ch_ptr->global_peak = global_peak; + per_ch_ptr->max_wait_smps_m1 = max_wait_smps_m1; + per_ch_ptr->target_gain_q27 = target_gain_q27; + per_ch_ptr->gain_q27 = gain_q27; + + return; +} + +/*---------------------------------------------------------------------------- +Detects zero crossing on the input signal and updates the limiter data +structure. The limiter alogorithm is based on Pei Xiang's investigation and +modification of a limiter technique based on updating gain at the zero-xing. + +Dan Mapes-Riordan and W.Marshall Leach, "The Design of a digital signal peak +limiter for audio signal processing," Journal of Audio Engineering Society, +Vol. 36, No. 7/8, 1988 July/August. + +Updates the limiter gain based on local maxima peak searching and zero-crossing +locations and applies the gain on the input data. +----------------------------------------------------------------------------*/ +static void process_delay_zc(limiter_per_ch_t *per_ch_ptr, int32 *scratch32, int32 dly_smps_m1, int32 samples) +{ + int32 j; + int64 accu64, prod64; + int32 inpL32, accu32, attn32, absL32; + int32 current_zc_buf_value, iq32; + int32 cur_idx, prev_zc_idx, local_max_peak, prev_sample_l32; + int32 threshold, gc; + int32 gain_var_q27, gain_q27; + limiter_tuning_v2_t *tuning_ptr = &per_ch_ptr->tuning_params; + + threshold = per_ch_ptr->shifted_threshold; + gc = tuning_ptr->gc; + + // Index for current sample in the zero-crossing buffer/input buffer + cur_idx = per_ch_ptr->cur_idx; + prev_zc_idx = per_ch_ptr->prev_zc_idx; + local_max_peak = per_ch_ptr->local_max_peak; + prev_sample_l32 = per_ch_ptr->prev_sample_l32; + + gain_var_q27 = per_ch_ptr->gain_var_q27; + gain_q27 = per_ch_ptr->gain_q27; + + for (j = 0; j < samples; ++j) + { + + // Extract and store the current input data + inpL32 = scratch32[j]; + + // Compute the absolute magnitude of the input + absL32 = (int32)u32_abs_s32_sat(inpL32); + + /************************************************************************ + Compute the zero-crossing locations and local maxima in the input audio - start + *************************************************************************/ + current_zc_buf_value = per_ch_ptr->zc_buf[cur_idx]; + + // Detect zero-crossing in the data + prod64 = s64_mult_s32_s32(inpL32, prev_sample_l32); + + // Update the previous sample + prev_sample_l32 = inpL32; + + /*********************************************************************** + Special case: No zero-crossings during the buffer duration. + If max delayline is reached and we have to force a zero crossing. + This is identified when the zcIndex reaches prev_zc_idx. By design, + prev_zc_idx is always chasing zcIndex at any zc, so when zcIndex + catches up with prev_zc_idx, it means zcIndex has searched more than + "delay" times and still not finding a zc. we'll force write the current + local max to this location for it to correctly update the gain afterwards. + ************************************************************************/ + if (cur_idx == prev_zc_idx) + { + // Store tracked local peak max to zc location + per_ch_ptr->zc_buf[cur_idx] = local_max_peak; + + current_zc_buf_value = local_max_peak; + + local_max_peak = absL32; // Reset local max value + } + // Zero-crossing condition: sample is exactly zero or it changes sign. + // If it's exactly zero, the loop is entered twice, but it's okay. + // Update the peak detection if zero-crossing is detected + else if (prod64 <= 0) + { + // Update zero-crossing buffer to store local maximum. + per_ch_ptr->zc_buf[prev_zc_idx] = local_max_peak; + + // Update previous zero-crossing index with the current buffer index + prev_zc_idx = cur_idx; + + local_max_peak = absL32; // Reset local maximum. + + // Note that so far just prev_zc_idx points to this location, + // Prepare for the next write to the location when next zc is + // reached, but nothing has been written at this location in zcBuffer yet + } + else + { + + // Mark non zero-crossing reference sample as zero. + per_ch_ptr->zc_buf[cur_idx] = 0; + + // Continue searching if zero-crossing is not detected + local_max_peak = s32_max_s32_s32(local_max_peak, absL32); + } + + /************************************************************************ + Compute the limiter gain and update it at zero-crossings - start + ************************************************************************/ + + // Update the limiter gain if zero-crossing is detected + if (current_zc_buf_value != 0) + { + // Compute the new gain as (1-GRC*gainVar) - March 2013 optimizations + gain_var_q27 = s32_mult_s32_s16_rnd_sat(gain_var_q27, (int16)gc); + + // If peak is not above the threshold, do a gain release. + // Release slowly using gain recovery coefficient (Q15 multiplication) + // If peak is above the threshold, attack is required, then both gain_q15 and gain_var_q15 + // will be updated again with attenuations in gain_q15 + gain_q27 = s32_sub_s32_s32_sat(c_gain_unity, gain_var_q27); + + // Buffer data x gain - Multiply 32x16 round, shift and sat in one cycle + accu32 = s32_saturate_s64(s64_mult_s32_s32_shift(current_zc_buf_value, gain_q27, 5)); + + // If the peak in the data is above the threshold, attack the gain and reduce it. + if (accu32 > threshold) + { + // Use Q6 DSP's linear approximation division routine to lower down the MIPS + // The inverse is computed with a normalized shift factor + accu64 = dsplib_approx_divide(threshold, current_zc_buf_value); + attn32 = (int32)accu64; // Extract the normalized inverse + iq32 = (int32)(accu64 >> 32); // Extract the normalization shift factor + + // Shift the result to get the quotient in desired Q15 format + gain_q27 = s32_shl_s32_sat(attn32, (int16)iq32 + 27); + + // Attack gain immediately if the peak overshoots the threshold + gain_var_q27 = s32_sub_s32_s32_sat(c_gain_unity, gain_q27); + } + + } /* if(per_ch_ptr->zc_buf32[cur_idx] != 0) */ + + /************************************************************************ + Implementation of Limiter gain on the input data + *************************************************************************/ + // Gain application - Multiply and shift and round and sat (one cycle in Q6) + scratch32[j] = s32_saturate_s64( + s64_shl_s64(s64_add_s64_s64(s64_mult_s32_s32(per_ch_ptr->delay_buf[cur_idx], gain_q27), 0x4000000), -27)); + + // Store the new input sample in the input buffer + per_ch_ptr->delay_buf[cur_idx] = inpL32; + + cur_idx = s32_modwrap_s32_u32(cur_idx + 1, dly_smps_m1 + 1); + + } /* for loop */ + + per_ch_ptr->cur_idx = cur_idx; + per_ch_ptr->prev_zc_idx = prev_zc_idx; + per_ch_ptr->local_max_peak = local_max_peak; + per_ch_ptr->prev_sample_l32 = prev_sample_l32; + per_ch_ptr->gain_var_q27 = gain_var_q27; + per_ch_ptr->gain_q27 = gain_q27; + + return; +} + +/*---------------------------------------------------------------------------- +In delay-less implementation, the limiter gains are updated immediately if +the instantaneous peak exceeds the threshold. The gain is also updated if the +time interval between gain updates exceeds the specified wait time. +----------------------------------------------------------------------------*/ +static void process_delayless_zc(limiter_per_ch_t *per_ch_ptr, int32 *scratch32, int32 samples) +{ + int j; + int64 accu64, prod64; + int32 accu32, attn32, inpL32, absL32; + int16 gc; + int32 attnQ27, iq32; + int32 tmp32, gain_var_q27, gain_q27, new_gain_var_q27; + int32 prev_sample_l32, cur_idx, max_wait_smps_m1; + int32 threshold; + limiter_tuning_v2_t *tuning_ptr = &per_ch_ptr->tuning_params; + + threshold = per_ch_ptr->shifted_threshold; + + gc = (int16)(tuning_ptr->gc); + max_wait_smps_m1 = per_ch_ptr->max_wait_smps_m1; + + prev_sample_l32 = per_ch_ptr->prev_sample_l32; + cur_idx = per_ch_ptr->cur_idx; + gain_var_q27 = per_ch_ptr->gain_var_q27; + gain_q27 = per_ch_ptr->gain_q27; + + for (j = 0; j < samples; ++j) + { + // Extract and store the current input data + inpL32 = scratch32[j]; + + // Compute the absolute magnitude of the input + absL32 = (int32)u32_abs_s32_sat(inpL32); + + // Detect zero-crossing in the data + prod64 = s64_mult_s32_s32(inpL32, prev_sample_l32); + + // Compute maximum of gain vs. previous instantaneous gain - March 2011 Changes + // tmp16 = max(gain, 1-GRC*gainVar) + new_gain_var_q27 = s32_mult_s32_s16_rnd_sat(gain_var_q27, gc); + tmp32 = s32_sub_s32_s32_sat(c_gain_unity, new_gain_var_q27); + + // absolute input x gain - Multiply 32x16 round, shift and sat in one cycle + accu32 = s32_saturate_s64(s64_mult_s32_s32_shift(absL32, tmp32, 5)); + + // If the peak in the data is above the threshold, attack the gain immediately. + if (accu32 > threshold) + { + + // Use Q6 DSP's linear approximation division routine to lower down the MIPS + // The inverse is computed with a normalized shift factor + accu64 = dsplib_approx_divide(threshold, absL32); + attn32 = (int32)accu64; // Extract the normalized inverse + iq32 = (int32)(accu64 >> 32); // Extract the normalization shift factor + + // Shift the result to get the quotient in desired Q15 format + attnQ27 = s32_shl_s32_sat(attn32, (int16)iq32 + 27); + + // Attack gain immediately if the peak overshoots the threshold + gain_var_q27 = s32_sub_s32_s32_sat(c_gain_unity, attnQ27); + + // Re-set the wait time index counter + cur_idx = 0; + } + else if ((prod64 < 0) || (absL32 == 0) || (cur_idx > max_wait_smps_m1)) + { + // Gain release at zero-crossings or if the wait time is exceeded. + // Release slowly using gain recovery coefficient (Q15 multiplication) + gain_var_q27 = new_gain_var_q27; + + // Re-set the wait time index counter + cur_idx = 0; + } + + // Update the gain at the zero-crossings (no saturation) + gain_q27 = s32_sub_s32_s32(c_gain_unity, gain_var_q27); + + // Store previous sample in the memory + prev_sample_l32 = inpL32; + + // Increment the wait-time counter index in a circular buffer fashion + cur_idx++; + + /**************************************************************************** + Implementation of Limiter gain on the input data + *****************************************************************************/ + // Gain application - Multiply and shift and round and sat (one cycle in Q6) + scratch32[j] = s32_saturate_s64(s64_shl_s64(s64_add_s64_s64(s64_mult_s32_s32(inpL32, gain_q27), 0x4000000), -27)); + } // for loop for j + + per_ch_ptr->prev_sample_l32 = prev_sample_l32; + per_ch_ptr->cur_idx = cur_idx; + per_ch_ptr->gain_var_q27 = gain_var_q27; + per_ch_ptr->gain_q27 = gain_q27; + + return; +} + +static void apply_makeup_gain(limiter_private_t *obj_ptr, void **out_ptr, int32 samples, uint32_t ch) +{ + int64 accu64; + int32 * out_ptr32; + int16 * out_ptr16; + limiter_per_ch_t * per_ch_ptr = &obj_ptr->per_ch[ch]; + limiter_tuning_v2_t *tuning_ptr = &per_ch_ptr->tuning_params; + + if (32 == obj_ptr->static_vars.data_width) + { + out_ptr32 = (int32 *)out_ptr[ch]; + if (c_mgain_unity == tuning_ptr->makeup_gain) + { + buffer32_copy_v2(out_ptr32, obj_ptr->scratch_buf, samples); + } + else + { + for (uint32_t j = 0; j < samples; ++j) + { + // Multiply output with the Q7.16 make-up gain + accu64 = s64_add_s64_s32(s64_mult_s32_s32(obj_ptr->scratch_buf[j], per_ch_ptr->makeup_gain_q16), 0x8000); + // Copy the audio output from the local buffer + out_ptr32[j] = s32_saturate_s64(s64_shl_s64(accu64, -16)); + } + } + } + else + { // 16 bit + out_ptr16 = (int16 *)out_ptr[ch]; + if (c_mgain_unity == tuning_ptr->makeup_gain) + { + for (uint32_t j = 0; j < samples; ++j) + { + out_ptr16[j] = s16_saturate_s32(obj_ptr->scratch_buf[j]); + } + } + else + { + for (uint32_t j = 0; j < samples; ++j) + { + // Multiply 32bit output with the Q7.16 make-up gain, and rounding + accu64 = s64_add_s64_s32(s64_mult_s32_s32(obj_ptr->scratch_buf[j], per_ch_ptr->makeup_gain_q16), 0x8000); + // Copy the audio output from the local buffer + out_ptr16[j] = s16_extract_s64_h_sat(accu64); + } + } + } // end of 16 bit makeup gain +} + +#endif // LIM_ASM + +/*---------------------------------------------------------------------------- +Pass data with just delay, no limiter processing. During transition from +limiter processing to no-limiter processing, fade out the existing limiter gain +gradually so as to minimize sudden discontinuities. +----------------------------------------------------------------------------*/ +static void pass_data(limiter_per_ch_t *per_ch_ptr, + int32 * scratch32, + int32 dly_smps_m1, + int32 history_winlen, + int32 samples) +{ + int j; + int32 inp32 = 0; + int32 cur_idx; + int32 gain_q27; + + // handling zero delay case + if (dly_smps_m1 < 0) + { + // Perform gain release to bring the gain back to unity + if (per_ch_ptr->gain_q27 < c_gain_unity) + { + per_ch_ptr->gain_var_q27 = s32_sub_s32_s32_sat(c_gain_unity, per_ch_ptr->gain_q27); + + // Release the gain variable gradually to smooth out discontinuities + per_ch_ptr->gain_var_q27 = s32_mult_s32_s16_rnd_sat(per_ch_ptr->gain_var_q27, c_fade_grc); + + // Update the gain once every frame + per_ch_ptr->gain_q27 = s32_sub_s32_s32(c_gain_unity, per_ch_ptr->gain_var_q27); + + for (j = 0; j < samples; j++) + { + // Apply limiter gain on the data + // Gain application - (one cycle in Q6) + //*scratch32++ = s32_saturate_s64(s64_mult_s32_s32_shift(*scratch32, per_ch_ptr->gain_q27, 5)); + scratch32[j] = s32_saturate_s64(s64_mult_s32_s32_shift(scratch32[j], per_ch_ptr->gain_q27, 5)); + } + } + else + { + // nop + } + } + else + { + // Perform gain release to bring the gain back to unity + + if (per_ch_ptr->gain_q27 < c_gain_unity) // Perform gain release to bring the gain back to unity + { + cur_idx = per_ch_ptr->cur_idx; + if (samples <= per_ch_ptr->bypass_sample_counter) + { + gain_q27 = per_ch_ptr->gain_q27; + for (j = 0; j < samples; ++j) + { + // Extract and store the current input data + inp32 = *scratch32; + + // Apply limiter gain on the data + // Gain application - (one cycle in Q6) + *scratch32++ = s32_saturate_s64(s64_mult_s32_s32_shift(per_ch_ptr->delay_buf[cur_idx], gain_q27, 5)); + + // Update the input buffer + per_ch_ptr->delay_buf[cur_idx] = inp32; + + // Increment the zero-crossing index for the circular buffer + cur_idx = s32_modwrap_s32_u32(cur_idx + 1, dly_smps_m1 + 1); + + // Update temp gain + gain_q27 = s32_add_s32_s32(gain_q27, per_ch_ptr->bypass_delta_gain_q27); + } + // Update global variables + if (gain_q27 > c_gain_unity) + { + gain_q27 = c_gain_unity; + } + per_ch_ptr->gain_q27 = gain_q27; + per_ch_ptr->bypass_sample_counter -= samples; + } + else + { + gain_q27 = per_ch_ptr->gain_q27; + for (j = 0; j < per_ch_ptr->bypass_sample_counter; ++j) + { + // Extract and store the current input data + inp32 = *scratch32; + + // Apply limiter gain on the data + // Gain application - (one cycle in Q6) + *scratch32++ = s32_saturate_s64(s64_mult_s32_s32_shift(per_ch_ptr->delay_buf[cur_idx], gain_q27, 5)); + + // Update the input buffer + per_ch_ptr->delay_buf[cur_idx] = inp32; + + // Increment the zero-crossing index for the circular buffer + cur_idx = s32_modwrap_s32_u32(cur_idx + 1, dly_smps_m1 + 1); + + // Update temp gain + gain_q27 = s32_add_s32_s32(gain_q27, per_ch_ptr->bypass_delta_gain_q27); + } + // Update global variables + if (gain_q27 != c_gain_unity) + { + gain_q27 = c_gain_unity; + } + per_ch_ptr->gain_q27 = gain_q27; + + // handling the rest of transition samples + for (j = 0; j < (samples - per_ch_ptr->bypass_sample_counter); ++j) + { + // Extract and store the current input data + inp32 = *scratch32; + + // Apply limiter gain on the data + // Gain application - (one cycle in Q6) + //*scratch32++ = s32_saturate_s64(s64_mult_s32_s32_shift(per_ch_ptr->delay_buf[cur_idx], + //per_ch_ptr->gain_q27, 5)); + + // copy input to delay buffer; no gain is applied as the gain is unity. + *scratch32++ = per_ch_ptr->delay_buf[cur_idx]; + + // Update the input buffer + per_ch_ptr->delay_buf[cur_idx] = inp32; + + // Increment the zero-crossing index for the circular buffer + cur_idx = s32_modwrap_s32_u32(cur_idx + 1, dly_smps_m1 + 1); + } + // set the bypass_sample_counter 0 + per_ch_ptr->bypass_sample_counter = 0; + } + + per_ch_ptr->cur_idx = cur_idx; // save the index after the loop + per_ch_ptr->prev_sample_l32 = inp32; + } + else + { + + cur_idx = per_ch_ptr->cur_idx; + + // Pass the delayed buffer data to the output + for (j = 0; j < samples; ++j) + { + // Extract and store the current input data + inp32 = *scratch32; + + // Place the delay buffer data into the output + *scratch32++ = per_ch_ptr->delay_buf[cur_idx]; + + // Update the input buffer + per_ch_ptr->delay_buf[cur_idx] = inp32; + + // Increment the zero-crossing index for the circular buffer + cur_idx = s32_modwrap_s32_u32(cur_idx + 1, dly_smps_m1 + 1); + } + + per_ch_ptr->cur_idx = cur_idx; // save the index after the loop + per_ch_ptr->prev_sample_l32 = inp32; + } + } + // Do one-time initialization of the zero-crossing buffers and memory variables + if (per_ch_ptr->bypass_sample_counter > 0) + { + per_ch_ptr->fade_flag = 1; + } + else + { + per_ch_ptr->fade_flag = 0; + } + + if ((0 == per_ch_ptr->fade_flag) && (1 == per_ch_ptr->fade_flag_prev)) + { + // Reset the history peak buffer + if (history_winlen > 0) + { + buffer32_empty_v2(per_ch_ptr->history_peak_buf, c_local_peak_bufsize); + // Reset the local max peak + per_ch_ptr->local_max_peak = (int32)0x0001; + + // Reset the global peak + per_ch_ptr->global_peak = (int32)0x0001; + + per_ch_ptr->peak_subbuf_idx = 0; // init current peak buffer index + per_ch_ptr->prev_peak_idx = 0; // init the previous index + per_ch_ptr->target_gain_q27 = c_gain_unity; // unity gain (Q27) + } + else if (dly_smps_m1 > 0) + { + // Reset the zero-crossing buffer + buffer32_empty_v2(per_ch_ptr->zc_buf, dly_smps_m1 + 1); + + // Reset the previous sample + // per_ch_ptr->prev_sample_l32 = 0; + // per_ch_ptr->prev_sample_l32 = (int32)0x7FFF; + + per_ch_ptr->prev_zc_idx = 0; + per_ch_ptr->gain_var_q27 = 0; + + per_ch_ptr->local_max_peak = 0; + } + else + { + per_ch_ptr->gain_var_q27 = 0; + } + per_ch_ptr->gain_q27 = c_gain_unity; // unity gain (Q27) + } + // update the previous fade flag + per_ch_ptr->fade_flag_prev = per_ch_ptr->fade_flag; +} + +/*---------------------------------------------------------------------------- + API Functions +----------------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------------- +// ** Processing one block of samples +// Description: Process multi-channel input audio sample by sample and limit + the input to specified threshold level. The input can be in + any sampling rate: 8, 16, 22.05, 32, 44.1, 48, 96, 192 KHz. + The input is 32-bit Q23 and the output is 32-bit Q23. + Implements zero-crossing update based limiter to limit the + input audio signal. The process function separates out delayed + and delay-less implementation. +// lib_ptr: [in] pointer to library structure +// out_ptr: [out] pointer to output sample block (multi channel double ptr) +// in_ptr: [in] pointer to input sample block (multi channel double ptr) +// samples: [in] number of samples to be processed per channel +----------------------------------------------------------------------------*/ +LIMITER_RESULT limiter_process(limiter_lib_t *lib_ptr, void **out_ptr, int32 **in_ptr, uint32 api_samples) +{ + limiter_private_t *obj_ptr = (limiter_private_t *)lib_ptr->mem_ptr; + limiter_per_ch_t * per_ch_ptr; + int32 ch; + int32 samples = (int32)api_samples; + + /* check if block size is within range */ + if (samples > obj_ptr->static_vars.max_block_size) + { + return LIMITER_FAILURE; + } + + /* process per channel */ + for (ch = 0; ch < obj_ptr->static_vars.num_chs; ++ch) + { + per_ch_ptr = &obj_ptr->per_ch[ch]; + + // copy input into scratch buffer + buffer32_copy_v2(obj_ptr->scratch_buf, in_ptr[ch], samples); + + // proc according to limiter processing modes + if (NORMAL_PROC == obj_ptr->mode) + { + /* when switch from non-bypass to bypass, a fade flag is set */ // move to set_param + if (1 == obj_ptr->bypass) + { + /* no limiting but apply delay only & handling transition */ + pass_data(per_ch_ptr, + obj_ptr->scratch_buf, + obj_ptr->dly_smps_m1, + obj_ptr->static_vars.history_winlen, + samples); + } +#ifdef LIM_ASM + else if (obj_ptr->static_vars.history_winlen > 0) + { + process_with_history_peak_asm(per_ch_ptr, + obj_ptr->scratch_buf, + obj_ptr->dly_smps_m1, + samples, + obj_ptr->static_vars.q_factor); + } + else if (obj_ptr->dly_smps_m1 >= 0) + { + /* zc limiter with delay */ + process_delay_zc_asm(per_ch_ptr, obj_ptr->scratch_buf, obj_ptr->dly_smps_m1, samples); + } + else + { + /* delayless zc limiter implementation */ + process_delayless_zc_asm(per_ch_ptr, obj_ptr->scratch_buf, samples); + } + } // if ( NORMAL_PROC ) + + // apply makeup gain + apply_makeup_gain_asm(obj_ptr, out_ptr, samples, ch); +#else // LIM_ASM + else if (obj_ptr->static_vars.history_winlen > 0) + { + process_with_history_peak(per_ch_ptr, + obj_ptr->scratch_buf, + obj_ptr->dly_smps_m1, + samples, + obj_ptr->static_vars.q_factor); + } + else if (obj_ptr->dly_smps_m1 >= 0) + { + /* zc limiter with delay */ + process_delay_zc(per_ch_ptr, obj_ptr->scratch_buf, obj_ptr->dly_smps_m1, samples); + } + else + { + /* delayless zc limiter implementation */ + process_delayless_zc(per_ch_ptr, obj_ptr->scratch_buf, samples); + } + } // if ( NORMAL_PROC ) + + // apply makeup gain + apply_makeup_gain(obj_ptr, out_ptr, samples, ch); +#endif // LIM_ASM + } /* ch index loop */ + + return LIMITER_SUCCESS; +} + +/*---------------------------------------------------------------------------- +// ** Get library memory requirements +// mem_req_ptr: [out] pointer to mem requirements structure +// limiter_static_vars_t: [in] pointer to static variable structure +----------------------------------------------------------------------------*/ +LIMITER_RESULT limiter_get_mem_req(limiter_mem_req_t *mem_req_ptr, limiter_static_vars_t *static_vars_ptr) +{ + uint32 reqsize; + int32 ch, delay_samples; + int32 sample_rate = static_vars_ptr->sample_rate; + int32 max_block_size = static_vars_ptr->max_block_size; + int32 num_chs = static_vars_ptr->num_chs; + + reqsize = smm_malloc_size(sizeof(limiter_private_t)); + + // alloc lim data struct according to num of channels + reqsize += smm_calloc_size(num_chs, sizeof(limiter_per_ch_t)); + + // per channel calc + delay_samples = s16_saturate_s32(s32_mult_s32_s16_rnd_sat(sample_rate, s16_saturate_s32(static_vars_ptr->delay))); + + for (ch = 0; ch < num_chs; ++ch) + { + reqsize += smm_calloc_size(delay_samples, sizeof(int32)); // delay buf + reqsize += smm_calloc_size(delay_samples, sizeof(int32)); // zerocrossing buf + } + + // scratch buf + reqsize += smm_calloc_size(max_block_size, sizeof(int32)); + + // assign mem sizes + mem_req_ptr->mem_size = reqsize; + mem_req_ptr->stack_size = c_limiter_max_stack_size; + + return LIMITER_SUCCESS; +} + +/*---------------------------------------------------------------------------- +// ** Get library memory requirements +// mem_req_ptr: [out] pointer to mem requirements structure +// limiter_static_vars_v2_t: [in] pointer to static variable structure v2 +----------------------------------------------------------------------------*/ +LIMITER_RESULT limiter_get_mem_req_v2(limiter_mem_req_t *mem_req_ptr, limiter_static_vars_v2_t *static_vars_v2_ptr) +{ + uint32 reqsize; + int32 ch, delay_samples; + int32 sample_rate = static_vars_v2_ptr->sample_rate; + int32 max_block_size = static_vars_v2_ptr->max_block_size; + int32 num_chs = static_vars_v2_ptr->num_chs; + int32 history_winlen = static_vars_v2_ptr->history_winlen; + + reqsize = smm_malloc_size(sizeof(limiter_private_t)); + + // alloc lim data struct according to num of channels + reqsize += smm_calloc_size(num_chs, sizeof(limiter_per_ch_t)); + + // per channel calc + delay_samples = s16_saturate_s32(s32_mult_s32_s16_rnd_sat(sample_rate, s16_saturate_s32(static_vars_v2_ptr->delay))); + + for (ch = 0; ch < num_chs; ++ch) + { + reqsize += smm_calloc_size(delay_samples, sizeof(int32)); // delay buf + if (history_winlen > 0) + { + reqsize += smm_calloc_size(c_local_peak_bufsize, sizeof(int32)); // history peak buf + } + + reqsize += smm_calloc_size(delay_samples, sizeof(int32)); // zerocrossing buf + } + + // scratch buf + reqsize += smm_calloc_size(max_block_size, sizeof(int32)); + + // assign mem sizes + mem_req_ptr->mem_size = reqsize; + mem_req_ptr->stack_size = c_limiter_max_stack_size; + + return LIMITER_SUCCESS; +} + +/*---------------------------------------------------------------------------- +// ** Partition, initialize memory, and set default values +// lib_ptr: [in, out] pointer to library structure +// static_vars_ptr: [in] pointer to static variable structure +// mem_ptr: [in] pointer to allocated memory +// mem_size: [in] size of memory allocated +----------------------------------------------------------------------------*/ +LIMITER_RESULT limiter_init_mem(limiter_lib_t * lib_ptr, + limiter_static_vars_t *static_vars_ptr, + void * mem_ptr, + uint32 mem_size) +{ + SimpleMemMgr MemMgr; + SimpleMemMgr * smm = &MemMgr; + limiter_mem_req_t mem_req; + limiter_private_t *obj_ptr; + limiter_per_ch_t * per_ch_ptr; + int32 num_chs = static_vars_ptr->num_chs; + int32 data_width = static_vars_ptr->data_width; + int32 sample_rate = static_vars_ptr->sample_rate; + int32 max_block_size = static_vars_ptr->max_block_size; + int32 delay_samples, ch; + + /* double check if allocated mem is enough */ + limiter_get_mem_req(&mem_req, static_vars_ptr); + if (mem_req.mem_size > mem_size) + { + return LIMITER_MEMERROR; + } + + /* assign initial mem pointers and allocate current instance */ + lib_ptr->mem_ptr = mem_ptr; + smm_init(smm, lib_ptr->mem_ptr); + obj_ptr = (limiter_private_t *)smm_malloc(smm, sizeof(limiter_private_t)); + + /* keep a copy of static vars */ + memscpy(&obj_ptr->static_vars, sizeof(limiter_static_vars_t), static_vars_ptr, sizeof(limiter_static_vars_t)); + obj_ptr->static_vars.q_factor = data_width == 16 ? 15 : 27; + obj_ptr->static_vars.history_winlen = 0; + + /* calculate delay samples */ + delay_samples = s16_saturate_s32(s32_mult_s32_s16_rnd_sat(sample_rate, s16_saturate_s32(static_vars_ptr->delay))); + obj_ptr->dly_smps_m1 = delay_samples - 1; + obj_ptr->bypass_smps = ms_to_sample_lim(BYPASS_TRANSITION_PERIOD_MSEC, static_vars_ptr->sample_rate); + + /* alloc per ch struct array */ + obj_ptr->per_ch = (limiter_per_ch_t *)smm_calloc(smm, num_chs, sizeof(limiter_per_ch_t)); + for (ch = 0; ch < num_chs; ++ch) + { + per_ch_ptr = &obj_ptr->per_ch[ch]; + per_ch_ptr->zc_buf = (int32 *)smm_calloc(smm, delay_samples, sizeof(int32)); // zc buf + per_ch_ptr->delay_buf = (int32 *)smm_calloc(smm, delay_samples, sizeof(int32)); // delay buf + } + + /* alloc scratch buffer */ + obj_ptr->scratch_buf = (int32 *)smm_calloc(smm, max_block_size, sizeof(int32)); + + /* set default values */ + set_default(obj_ptr); + + return LIMITER_SUCCESS; +} + +/*---------------------------------------------------------------------------- +// ** Partition, initialize memory, and set default values +// lib_ptr: [in, out] pointer to library structure +// static_vars_v2_ptr: [in] pointer to static variable structure v2 +// mem_ptr: [in] pointer to allocated memory +// mem_size: [in] size of memory allocated +----------------------------------------------------------------------------*/ +LIMITER_RESULT limiter_init_mem_v2(limiter_lib_t * lib_ptr, + limiter_static_vars_v2_t *static_vars_v2_ptr, + void * mem_ptr, + uint32 mem_size) +{ + SimpleMemMgr MemMgr; + SimpleMemMgr * smm = &MemMgr; + limiter_mem_req_t mem_req; + limiter_private_t *obj_ptr; + limiter_per_ch_t * per_ch_ptr; + int32 num_chs = static_vars_v2_ptr->num_chs; + int32 sample_rate = static_vars_v2_ptr->sample_rate; + int32 max_block_size = static_vars_v2_ptr->max_block_size; + int32 history_winlen = static_vars_v2_ptr->history_winlen; + int32 delay_samples, ch; + + /* double check if allocated mem is enough */ + limiter_get_mem_req_v2(&mem_req, static_vars_v2_ptr); + if (mem_req.mem_size > mem_size) + { + return LIMITER_MEMERROR; + } + + /* assign initial mem pointers and allocate current instance */ + lib_ptr->mem_ptr = mem_ptr; + smm_init(smm, lib_ptr->mem_ptr); + obj_ptr = (limiter_private_t *)smm_malloc(smm, sizeof(limiter_private_t)); + + /* keep a copy of static vars */ + obj_ptr->static_vars = *static_vars_v2_ptr; + + /* calculate delay samples */ + delay_samples = s16_saturate_s32(s32_mult_s32_s16_rnd_sat(sample_rate, s16_saturate_s32(static_vars_v2_ptr->delay))); + obj_ptr->dly_smps_m1 = delay_samples - 1; + obj_ptr->bypass_smps = ms_to_sample_lim(BYPASS_TRANSITION_PERIOD_MSEC, static_vars_v2_ptr->sample_rate); + + /* alloc per ch struct array */ + obj_ptr->per_ch = (limiter_per_ch_t *)smm_calloc(smm, num_chs, sizeof(limiter_per_ch_t)); + for (ch = 0; ch < num_chs; ++ch) + { + per_ch_ptr = &obj_ptr->per_ch[ch]; + if (history_winlen > 0) + { + per_ch_ptr->history_peak_buf = + (int32 *)smm_calloc(smm, c_local_peak_bufsize, sizeof(int32)); // history peak buf + } + + per_ch_ptr->zc_buf = (int32 *)smm_calloc(smm, delay_samples, sizeof(int32)); // zc buf + per_ch_ptr->delay_buf = (int32 *)smm_calloc(smm, delay_samples, sizeof(int32)); // delay buf + } + + /* alloc scratch buffer */ + obj_ptr->scratch_buf = (int32 *)smm_calloc(smm, max_block_size, sizeof(int32)); + + /* set default values */ + set_default(obj_ptr); + + return LIMITER_SUCCESS; +} + +/*---------------------------------------------------------------------------- +// ** Set parameters to library +// lib_ptr: [in, out] pointer to lib structure +// param_id: [in] parameter id +// param_ptr: [in] pointer to the memory where the new values are stored +// param_size:[in] size of the memory pointed by param_ptr +----------------------------------------------------------------------------*/ +LIMITER_RESULT limiter_set_param(limiter_lib_t *lib_ptr, uint32 param_id, void *mem_ptr, uint32 mem_size) +{ + limiter_private_t * obj_ptr = (limiter_private_t *)lib_ptr->mem_ptr; + limiter_tuning_t * tuning_ptr; + limiter_tuning_v2_t *tuning_v2_ptr; + limiter_mode_t new_mode; + limiter_bypass_t new_bypass; + + switch (param_id) + { + + case LIMITER_PARAM_MODE: + // set limiter modes + // MAKEUPGAIN_ONLY : apply makeup gain only, and no delay + // NORMAL_PROC : normal limiter processing + if (mem_size == sizeof(limiter_mode_t)) + { + new_mode = *((limiter_mode_t *)mem_ptr); + + // sanity check of limiter mode + if ((MAKEUPGAIN_ONLY != new_mode) && (NORMAL_PROC != new_mode)) + { + return LIMITER_FAILURE; + } + // since delay changes between modes, need to flush mem + if (obj_ptr->mode != new_mode) + { + obj_ptr->mode = new_mode; + reset(obj_ptr); + } + } + else + { + return LIMITER_MEMERROR; // parameter size doesn't match + } + break; + + case LIMITER_PARAM_BYPASS: + if (mem_size == sizeof(limiter_bypass_t)) + { + int32 ch; + int32 delta_gain; + + new_bypass = *((limiter_bypass_t *)mem_ptr); + if (new_bypass != 0) + { + new_bypass = 1; + } + if (obj_ptr->bypass != new_bypass) + { + obj_ptr->bypass = new_bypass; + if (obj_ptr->bypass == 1) + { + // SCHI: bypass mode + for (ch = 0; ch < obj_ptr->static_vars.num_chs; ch++) + { + if (obj_ptr->per_ch[ch].gain_q27 != c_gain_unity) + { + delta_gain = s32_sub_s32_s32(c_gain_unity, obj_ptr->per_ch[ch].gain_q27); + obj_ptr->per_ch[ch].bypass_delta_gain_q27 = + divide_int32_qx(delta_gain, obj_ptr->bypass_smps, 0); // Q27 + obj_ptr->per_ch[ch].bypass_sample_counter = obj_ptr->bypass_smps; + obj_ptr->per_ch[ch].fade_flag = 1; + } + else + { + obj_ptr->per_ch[ch].bypass_delta_gain_q27 = 0; // Q27 + obj_ptr->per_ch[ch].bypass_sample_counter = 0; + obj_ptr->per_ch[ch].fade_flag = 0; + } + } + } + } + } + else + { + return LIMITER_MEMERROR; // parameter size doesn't match + } + break; + + case LIMITER_PARAM_TUNING: + if (mem_size == sizeof(limiter_tuning_t)) + { + tuning_ptr = (limiter_tuning_t *)mem_ptr; + if (tuning_ptr->ch_idx < obj_ptr->static_vars.num_chs) + { + // obj_ptr->per_ch[tuning_ptr->ch_idx].tuning_params = *tuning_ptr; + memscpy(&obj_ptr->per_ch[tuning_ptr->ch_idx].tuning_params, mem_size, tuning_ptr, mem_size); + // convert tuning params + convert_tuning_params(obj_ptr, tuning_ptr->ch_idx); + // and no need for mem flush when setting these params + } + else + { + return LIMITER_FAILURE; // channel idx out of range + } + } + else + { + return LIMITER_MEMERROR; // parameter size doesn't match + } + break; + + case LIMITER_PARAM_TUNING_V2: + if (mem_size == sizeof(limiter_tuning_v2_t)) + { + tuning_v2_ptr = (limiter_tuning_v2_t *)mem_ptr; + if (tuning_v2_ptr->ch_idx < obj_ptr->static_vars.num_chs) + { + obj_ptr->per_ch[tuning_v2_ptr->ch_idx].tuning_params = *tuning_v2_ptr; + // convert tuning params + convert_tuning_params(obj_ptr, tuning_v2_ptr->ch_idx); + // and no need for mem flush when setting these params + } + else + { + return LIMITER_FAILURE; // channel idx out of range + } + } + else + { + return LIMITER_MEMERROR; // parameter size doesn't match + } + break; + + case LIMITER_PARAM_RESET: + reset(obj_ptr); + break; + + default: + return LIMITER_FAILURE; // unidentified parma ID + } + + return LIMITER_SUCCESS; +} + +/*---------------------------------------------------------------------------- +// ** Get parameters from library +// lib_ptr: [in] pointer to library structure +// param_id: [in] parameter id +// param_ptr: [out] pointer to the memory where the retrieved value is going to be stored +// param_size:[in] size of the memory pointed by param_ptr +// param_actual_size_ptr: [out] pointer to memory that will hold the actual size of the parameter +----------------------------------------------------------------------------*/ +LIMITER_RESULT limiter_get_param(limiter_lib_t *lib_ptr, + uint32 param_id, + void * mem_ptr, + uint32 mem_size, + uint32 * param_size_ptr) +{ + limiter_private_t * obj_ptr = (limiter_private_t *)lib_ptr->mem_ptr; + limiter_tuning_t * tuning_ptr; + limiter_tuning_v2_t *tuning_v2_ptr; + + switch (param_id) + { + + case LIMITER_PARAM_LIB_VER: + // get library version + if (mem_size >= sizeof(limiter_version_t)) + { + *((limiter_version_t *)mem_ptr) = LIMITER_LIB_VER; + *param_size_ptr = sizeof(limiter_version_t); + } + else + { + return LIMITER_MEMERROR; + } + break; + + case LIMITER_PARAM_MODE: + if (mem_size >= sizeof(limiter_mode_t)) + { + *((limiter_mode_t *)mem_ptr) = obj_ptr->mode; + *param_size_ptr = sizeof(limiter_mode_t); + } + else + { + return LIMITER_MEMERROR; + } + break; + + case LIMITER_PARAM_BYPASS: + if (mem_size >= sizeof(limiter_bypass_t)) + { + *((limiter_bypass_t *)mem_ptr) = obj_ptr->bypass; + *param_size_ptr = sizeof(limiter_bypass_t); + } + else + { + return LIMITER_MEMERROR; + } + break; + + case LIMITER_PARAM_TUNING: + // get tuning parameters for certain obj_ptr->per_ch[tuning_ptr->ch_idx] in channel + // caller must specify channel index inside the structure first + if (mem_size >= sizeof(limiter_tuning_t)) + { + tuning_ptr = (limiter_tuning_t *)mem_ptr; + if (tuning_ptr->ch_idx < obj_ptr->static_vars.num_chs) + { + //*tuning_ptr = obj_ptr->per_ch[tuning_ptr->ch_idx].tuning_params; + memscpy(tuning_ptr, + sizeof(limiter_tuning_t), + &obj_ptr->per_ch[tuning_ptr->ch_idx].tuning_params, + sizeof(limiter_tuning_t)); + *param_size_ptr = sizeof(limiter_tuning_t); + } + else + { + return LIMITER_FAILURE; // channel idx out of range + } + } + else + { + return LIMITER_MEMERROR; + } + break; + + case LIMITER_PARAM_TUNING_V2: + // get tuning parameters for certain obj_ptr->per_ch[tuning_ptr->ch_idx] in channel + // caller must specify channel index inside the structure first + if (mem_size >= sizeof(limiter_tuning_v2_t)) + { + tuning_v2_ptr = (limiter_tuning_v2_t *)mem_ptr; + if (tuning_v2_ptr->ch_idx < obj_ptr->static_vars.num_chs) + { + *tuning_v2_ptr = obj_ptr->per_ch[tuning_v2_ptr->ch_idx].tuning_params; + *param_size_ptr = sizeof(limiter_tuning_v2_t); + } + else + { + return LIMITER_FAILURE; // channel idx out of range + } + } + else + { + return LIMITER_MEMERROR; + } + break; + + default: + return LIMITER_FAILURE; + } + + return LIMITER_SUCCESS; +} diff --git a/modules/processing/gain_control/limiter/lib/src/limiter.h b/modules/processing/gain_control/limiter/lib/src/limiter.h new file mode 100644 index 0000000..c185581 --- /dev/null +++ b/modules/processing/gain_control/limiter/lib/src/limiter.h @@ -0,0 +1,87 @@ +#ifndef LIMITER_H +#define LIMITER_H +/*============================================================================ + @file limiter.h + + Private Header for Zero-Crossing Limiter, Peak-History Limiter + + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause + +============================================================================*/ +#include "limiter_api.h" + +#if ((defined __hexagon__) || (defined __qdsp6__)) +#define LIM_ASM +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/*---------------------------------------------------------------------------- + Constants +----------------------------------------------------------------------------*/ +#define BYPASS_TRANSITION_PERIOD_MSEC 10 +static const int32 c_1_over_1000_q31 = 2147484; // 0.001 in Q31 + +static const int32 c_limiter_max_stack_size = 2000; // worst case stack mem +static const int16 c_mgain_unity = 256; // unity makeup gain (1, Q8) +static const int32 c_gain_unity = 134217728; // limiter unity gain (1, Q27) +static const int32 c_unity_q15 = 32768; // limiter unity number (1, Q15) +static const uint32 c_unity_q31 = 0x80000000; // limiter unity number (1, Q31) +static const int16 c_fade_grc = 24576; // release const (0.75, Q15) +static const int16 c_local_peak_bufsize = 4; // history peak buffer size + +/*---------------------------------------------------------------------------- + * Type Declarations + * -------------------------------------------------------------------------*/ +typedef struct limiter_per_ch_t { // ** per channel variables + + limiter_tuning_v2_t tuning_params; // internal copy: tuning params + + int32 max_wait_smps_m1; // max wait sample count - 1 + int32 makeup_gain_q16; // left-shifted makeup gain in Q16 + int32 local_max_peak; // local maximum peak array + int32 prev_sample_l32; // previous sample in the buffer + int32 cur_idx; // current sample index + int32 prev_zc_idx; // previous zero-crossing idx array + int32 fade_flag; // fade flag for transition + int32 gain_q27; // limiter gain (q27) + int32 gain_var_q27; // limiter gain var (Q27) + + int32 shifted_threshold; // limiter threshold aligned to input q-factor + int32 shifted_hard_threshold; // limiter hard-threshold aligned to input q-factor + + int32 target_gain_q27; // target limiter gain (Q27) + int32 global_peak; // global peak + int32 peak_subbuf_idx; // current index for the history window shifting buffer + int32 prev_peak_idx; // previous zero-crossing idx array + + int32 *delay_buf; // delay buffer (32 bit) + int32 *zc_buf; // zero-crossing buffer (32 bit) + int32 *history_peak_buf; // history peak buffer (32 bit) + int32 fade_flag_prev; // fade flag for previous frame + int32 bypass_sample_counter; // bypass transition samples + int32 bypass_delta_gain_q27; + int32 reserved_var; +} limiter_per_ch_t; + +typedef struct limiter_private_t { // ** main private structure + + limiter_static_vars_v2_t static_vars; // internal copy: static vars + + limiter_mode_t mode; // processing mode + limiter_bypass_t bypass; // bypass flag + int32 dly_smps_m1; // delay sample count - 1 + + limiter_per_ch_t *per_ch; // per channel struct + int32 *scratch_buf; // scratch buffer + int32 bypass_smps; // bypass transition samples +} limiter_private_t; + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* LIMITER_H */ diff --git a/modules/processing/gain_control/limiter/lib/src/limiter24.c b/modules/processing/gain_control/limiter/lib/src/limiter24.c new file mode 100644 index 0000000..67f1950 --- /dev/null +++ b/modules/processing/gain_control/limiter/lib/src/limiter24.c @@ -0,0 +1,1042 @@ +/* + * Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +/***************************************************************************** +* FILE NAME: limiter24.c TYPE: C-source file +* DESCRIPTION: Implements the limiter algorithm. +*****************************************************************************/ + +/*---------------------------------------------------------------------------- +* Include Files +* -------------------------------------------------------------------------*/ + +#include "limiter24_api.h" +#include "limiter24.h" +#include "audio_basic_op.h" +#include "audio_basic_op_ext.h" +#include "audio_divide_qx.h" +#include + +#ifdef LIM_ASM +/*Functions defined in ASM */ +void lim_proc_delay_asm_16(LimCfgType *pCfg, + LimDataType *pData, int32 *in, void *out, + int16 iSubFrameSize); +void lim_proc_delay_asm_32(LimCfgType *pCfg, + LimDataType *pData, int32 *in, void *out, + int16 iSubFrameSize); + +void lim_proc_delayless_asm_16(LimCfgType *pCfg, + LimDataType *pData, int32 *in, void *out, + int16 iSubFrameSize); +void lim_proc_delayless_asm_32(LimCfgType *pCfg, + LimDataType *pData, int32 *in, void *out, + int16 iSubFrameSize); +#endif + +static unsigned int align_to_8_byte(const unsigned int num) +{ + return ((num + 7) & (0xFFFFFFF8)); +} + +static void apply_makeup_gain16(LimCfgType *pCfg, LimDataType *pData, + void** pOutFrame,int16 iSubFrameSize) +{ + int j; + int64 accu64; + /**************************************************************************** + Implementation of Make up gain on the limiter output + // out16 = (out32 * gain) << (7-gainexp); + // = out32 * ( gain << (7-gainexp) ); + *****************************************************************************/ + + int16 *pOutFrameL16 = (int16*)(*pOutFrame); + if (LIM_MGAIN_UNITY == pCfg->params.limMakeUpGain) + { + for(j=0; jptempBuffer[j]; + } + } + else + { + for(j=0; jptempBuffer[j],pCfg->mGainL32); + // Copy the audio output from the local buffer + pOutFrameL16[j] = s16_extract_s64_h_sat(accu64); + } + } + //update output frame data pointers + pOutFrameL16 += iSubFrameSize; + *pOutFrame = pOutFrameL16; + +} + +static void apply_makeup_gain32(LimCfgType *pCfg, LimDataType *pData, + void** pOutFrame,int16 iSubFrameSize) +{ + + int j; + int64 accu64; + /**************************************************************************** + Implementation of Make up gain on the limiter output + // out16 = (out32 * gain) << (7-gainexp); + // = out32 * ( gain << (7-gainexp) ); + *****************************************************************************/ + + int32 *pOutFrameL32 = (int32*)(*pOutFrame); + if (LIM_MGAIN_UNITY == pCfg->params.limMakeUpGain) + { + memscpy(pOutFrameL32, iSubFrameSize*sizeof(int32),pData->ptempBuffer, iSubFrameSize*sizeof(int32)); + } + else + { + for(j=0; jptempBuffer[j],pCfg->mGainL32); + // Copy the audio output from the local buffer + pOutFrameL32[j] = s32_saturate_s64(s64_shl_s64(accu64, -16)); + } + } + + //update output frame data pointers + pOutFrameL32 += iSubFrameSize; + *pOutFrame = pOutFrameL32; + +} +/*====================================================================== + +FUNCTION Lim_init_default + +DESCRIPTION Initialize 24bit limiter data structure with default +parameters. Sampling rate info and a pointer to static +memory in storage is passed in for configuring the +LIM static configuration structure. + +Called once at audio connection set up time. + +PARAMETERS params: [in] Pointer to tuning parameter list + +SIDE EFFECTS None + +======================================================================*/ + +int Lim_init_default(int32 *params, + int16 numChannels, + int16 bitsPerSample) +{ + int16 i; + + // Initialize tuning parameter with default values + for (i = 0; i < numChannels; i++) + { + params[i*LIM_PAR_TOTAL + LIM_ENABLE] = LIM_ENABLE_VAL; // Q0 Limiter enable word + params[i*LIM_PAR_TOTAL + LIM_MODE] = LIM_MODE_VAL; // Q0 Limiter mode word + if (32 == bitsPerSample) + { + params[i*LIM_PAR_TOTAL + LIM_THRESH] = LIM_THRESH_VAL32; // Q8.23 Limiter threshold + } + else if (16 == bitsPerSample) + { + params[i*LIM_PAR_TOTAL + LIM_THRESH] = LIM_THRESH_VAL16; // Q8.23 Limiter threshold + } + params[i*LIM_PAR_TOTAL + LIM_MAKEUP_GAIN] = LIM_MAKEUP_GAIN_VAL; // Q7.8 Limiter make-up gain + params[i*LIM_PAR_TOTAL + LIM_GC] = LIM_GC_VAL; // Q15 Limiter gain recovery coefficient + params[i*LIM_PAR_TOTAL + LIM_DELAY] = LIM_DELAY_VAL; // Q0.15 Limiter delay in seconds + params[i*LIM_PAR_TOTAL + LIM_MAX_WAIT] = LIM_MAX_WAIT_VAL; // Q0.15 Limiter waiting time in seconds + } + + ///////////////////////////////////////////////////////// + // End module default initialization + ///////////////////////////////////////////////////////// + return(0); +} + +/*====================================================================== + +FUNCTION Lim_init + +DESCRIPTION Performs initialization of data structures for the +LIM algorithm. Pointers to configuration and data +structures are passed in for configuration. + +Called once at audio connection set up time. + +PARAMETERS pLimCfgStruct: [in] Pointer to configuration structure +pLimDataStruct: [in] Pointer to data structure +params: [in] Pointer to tuning parameter list +samplingRate: [in] Input sampling rate + +SIDE EFFECTS None + +======================================================================*/ + +int Lim_init(CLimiterLib *pLimStruct, + int32 *params, + int16 chIdx, + int32 sampleRate, + int32 bitsPerSample) +{ + int32 accu32; + LimCfgType *pCfg = &pLimStruct->LimCfgStruct; + LimDataType *pLimDataStruct = &pLimStruct->LimDataStruct; + + pLimStruct->bitsPerSample = bitsPerSample; + + // Initialize limiter configuration structure: LimCfgType + + // Initialize Limiter tuning parameters + pCfg->params.limEnable = (int16)params[chIdx*LIM_PAR_TOTAL + LIM_ENABLE]; // Q0 Limiter enable flag + pCfg->params.limMode = (int16)params[chIdx*LIM_PAR_TOTAL + LIM_MODE]; // Q0 Limiter mode word + pCfg->params.limThresh = params[chIdx*LIM_PAR_TOTAL + LIM_THRESH]; // Limiter threshold:Q3.15 for 16bit; Q8.23 for 32bit + pCfg->params.limMakeUpGain = (int16) params[chIdx*LIM_PAR_TOTAL + LIM_MAKEUP_GAIN];// Q7.8 Limiter make-up gain + pCfg->params.limGc = (int16)params[chIdx*LIM_PAR_TOTAL + LIM_GC]; // Q15 Limiter gain recovery coefficient + pCfg->params.limDelay = (int16)params[chIdx*LIM_PAR_TOTAL + LIM_DELAY]; // Q15 Limiter delay in seconds + pCfg->params.limMaxWait = (int16)params[chIdx*LIM_PAR_TOTAL + LIM_MAX_WAIT]; // Q15 Limiter waiting time in seconds + + // Convert the delay and waiting time from Q15 seconds to Q0 samples + accu32 = s32_mult_s32_s16_rnd_sat(sampleRate, pCfg->params.limDelay ); + pCfg->params.limDelay = s16_saturate_s32(accu32); + + accu32 = s32_mult_s32_s16_rnd_sat(sampleRate, pCfg->params.limMaxWait); + pCfg->params.limMaxWait = s16_saturate_s32(accu32); + + // Calculate and store delay-minus-one sample in the static memory + pCfg->limDelayMinusOne = pCfg->params.limDelay-1; + + // Calculate and store max wait-minus-one sample in the static memory + pCfg->limWaitMinusOne = pCfg->params.limMaxWait-1; + + // Left shift the make up gain by (7+1) to Q7.16 before multiplication with the data + pCfg->mGainL32 = s32_shl_s32_sat((int32 )pCfg->params.limMakeUpGain, 8); + + pCfg->threshL32Q15 = pCfg->params.limThresh; + pCfg->limGc = pCfg->params.limGc ; + + if(bitsPerSample == 16) + { + /*Assign Function pointers based on delay and bitsPerSample */ + if( pCfg->params.limDelay > 0) + { +#ifdef LIM_ASM + pLimStruct->lim_proc = lim_proc_delay_asm_16; +#else + pLimStruct->lim_proc = lim_proc_delay; +#endif + } + else + { +#ifdef LIM_ASM + pLimStruct->lim_proc = lim_proc_delayless_asm_16; + +#else + pLimStruct->lim_proc = lim_proc_delayless; +#endif + } + + pLimStruct->apply_makeup_gain = apply_makeup_gain16; + } + else if (bitsPerSample == 32) + { + + if( pCfg->params.limDelay > 0) + { +#ifdef LIM_ASM + pLimStruct->lim_proc = lim_proc_delay_asm_32; +#else + pLimStruct->lim_proc = lim_proc_delay; +#endif + } + else + { +#ifdef LIM_ASM + pLimStruct->lim_proc = lim_proc_delayless_asm_32; +#else + pLimStruct->lim_proc = lim_proc_delayless; +#endif + } + + pLimStruct->apply_makeup_gain = apply_makeup_gain32; + } + + + // for delay = 0, we dont need these buffers, make them NULL + if(pCfg->params.limDelay == 0) + { + pLimDataStruct->pInpBuffer = NULL; + pLimDataStruct->pZcBuffer = NULL; + } + else + { + // Initialize limiter data structure pointers + pLimDataStruct->pInpBuffer = pLimStruct->pDelayBuf; + pLimDataStruct->pZcBuffer = pLimStruct->pZcBuf; + } + // Reset limiter data structure + Lim_DataStruct_Reset(pLimDataStruct, pCfg); + + ///////////////////////////////////////////////////////// + // End module initialization + ///////////////////////////////////////////////////////// + return 0; +} + +/*====================================================================== + + FUNCTION Lim_reinit + + DESCRIPTION Performs reinit of configuration structures without + modifiying the existing data structure for the + LIM algorithm. Pointers to configuration structures + are passed in for configuration. + + Called in scenarios when only configuration data needs + to be updated. + + PARAMETERS pLimCfgStruct: [in] Pointer to configuration structure + params: [in] Pointer to tuning parameter list + numChannels: [in] Number of channels + samplingRate: [in] Input sampling rate + + SIDE EFFECTS None + +======================================================================*/ + +int Lim_reinit(LimCfgType *pCfg, + int32 *params, + int32 sampleRate) +{ + int32 accu32; + + // Initialize limiter configuration structure: LimCfgType + // Re-initialize Limiter tuning parameters + pCfg->params.limThresh = params[LIM_THRESH]; // Limiter threshold:Q3.15 for 16bit; Q4.27 for 32bit + pCfg->params.limMakeUpGain = (int16) params[LIM_MAKEUP_GAIN]; // Q7.8 Limiter make-up gain + pCfg->params.limGc = (int16)params[LIM_GC]; // Q15 Limiter gain recovery coefficient + pCfg->params.limMaxWait = (int16)params[LIM_MAX_WAIT]; // Q15 Limiter waiting time in seconds + + accu32 = s32_mult_s32_s16_rnd_sat(sampleRate, pCfg->params.limMaxWait); + pCfg->params.limMaxWait = s16_saturate_s32(accu32); + + // Calculate and store max wait-minus-one sample in the static memory + pCfg->limWaitMinusOne = pCfg->params.limMaxWait-1; + + // Left shift the make up gain by (7+1) to Q7.16 before multiplication with the data + pCfg->mGainL32 = s32_shl_s32_sat((int32 )pCfg->params.limMakeUpGain, 8); + +#ifdef LIM_DEBUG + printf("Limiter Enable: %d \n", pCfg->params.limEnable); + printf("Limiter Mode: %d \n", pCfg->params.limMode); + printf("Limiter Threshold: %d \n", pCfg->params.limThresh); + printf("Limiter Makeup Gain: %d \n", pCfg->params.limMakeUpGain); + printf("Limiter Gain Coeff: %d \n", pCfg->params.limGc); + printf("Limiter Delay: %d \n", pCfg->params.limDelay); + printf("Limiter Wait-time: %d \n", pCfg->params.limMaxWait); +#endif + + + ///////////////////////////////////////////////////////// + // End module Reinitialization + ///////////////////////////////////////////////////////// + return (0); +} + + +/*====================================================================== + +FUNCTION Lim_DataStruct_Reset + +DESCRIPTION Performs reset of data structures for the +LIM algorithm. Pointers to the data +structure is passed in for reset. + +PARAMETERS pLimDataStruct: [in] Pointer to data structure + +SIDE EFFECTS None + +======================================================================*/ + +void Lim_DataStruct_Reset(LimDataType *pLimDataStruct, + LimCfgType *pLimCfgStruct) +{ + LimDataType *pData = pLimDataStruct; + + // Initialize the de-compressed LIM gain buffer + pData->gainQ15 = LIM_GAIN_UNITY; // Q15 (1) Unity gain + pData->gainVarQ15 = 0; // Initialize the gain variable + + // Initialize static members of the data structure + pData->curIndex = 0; // Initialize the current operating index + pData->prev0xIndex = 0; // Initialize the previous index + + pData->localMaxPeakL32 = 0; // Initialize the local maxima peak + pData->prevSampleL32 = 0; // Initialize the previous sample + + pData->fadeFlag = FADE_INIT; // Initialize the fade flag + + // Initialize the buffer memory to zero + buffer32_empty(pData->pInpBuffer, pLimCfgStruct->params.limDelay); + buffer32_empty(pData->pZcBuffer, pLimCfgStruct->params.limDelay); +} + + +/*====================================================================== + +FUNCTION Lim_process + +DESCRIPTION Process multi-channel input audio sample by sample and limit the +input to specified threshold level. The input can be in any sampling +rate - 8, 16, 22.05, 32, 44.1, 48, 96, 192 KHz. The input is 32-bit Q23 +and the output is 32-bit Q23. Implements zero-crossing update based +limiter to limit the input audio signal. The process function separates +out delayed and delay-less implementation. + +DEPENDENCIES Input pointers must not be NULL. +lim_init must be called prior to any call to lim_process. +Length of output buffer must be at least as large the +length of the input buffer. + +PARAMETERS pInpL32Q23: [in] Pointer to 32-bit Q23 multi-channel audio +pOutL32Q23: [out] Pointer to 32-bit Q23 output audio + +RETURN VALUE None. + +SIDE EFFECTS None. + +======================================================================*/ + +void Lim_process (CLimiterLib *pLimStruct, + void *pOut, + int32 *pInp, + int16 frameSize, + int16 bypass) +{ + int16 iSubFrameSize = LIMITER_BLOCKSIZE; + LimCfgType *pCfg = &pLimStruct->LimCfgStruct; + LimDataType *pData = &pLimStruct->LimDataStruct; + int16 iFrameSize; + int32 *pInpFrameL32; + void *pOutFrame = pOut; + int16 ChannelBypass; + int32 limMode = pCfg->params.limMode; +#ifdef LIM_ASM + int32 bitsPerSample = pLimStruct->bitsPerSample; +#endif + pInpFrameL32 = (int32 *)pInp; + + iFrameSize = frameSize; + ChannelBypass = bypass; + /**************************************************************************** + Process Limiter operation on the input audio frame data + ****************************************************************************/ + while ( iFrameSize > 0 ) + { + // Determine the sub frame size for processing. + // If the block length is larger than LIMITER_BLOCKSIZE, + // take LIMITER_BLOCKSIZE at a time for processing. + // For the remaining samples less than LIMITER_BLOCKSIZE, + // use that sample count directly. + if ( iFrameSize <= iSubFrameSize ) + { + iSubFrameSize = iFrameSize; + } + iFrameSize -= iSubFrameSize; // Decrement framesize + + if (1 == ChannelBypass) + { + // Copy the 32-bit input data into a temporary buffer + pData->ptempBuffer = pInpFrameL32; + if(FADE_INIT == pData->fadeFlag) + { + // Re-set limiter bypass mode just for this frame to do normal limiter processing + ChannelBypass = 0; + pData->fadeFlag = FADE_START; + } + + } + else // not bypass mode + { + // Copy the 32-bit input data into a temporary buffer + pData->ptempBuffer = pInpFrameL32; + pData->fadeFlag = FADE_INIT; // Reset fade-in flag + } + + // limiter processing + if(limMode) //limiter is enabled + { + if (1 == ChannelBypass) + { + /***************************************************************** + Delay only processing without limiting for single input streams + ******************************************************************/ + lim_pass_data(pCfg, pData, iSubFrameSize); + } + else // bypass + { + /***************************************************** + Limiter processing + *****************************************************/ + (*pLimStruct->lim_proc)(pCfg, pData, pData->ptempBuffer, pOutFrame, iSubFrameSize); + +#ifdef LIM_ASM + /* increment the output ptr*/ + pOutFrame = (char*)pOutFrame + iSubFrameSize*(bitsPerSample>>3); +#endif + + } + + }/* if(pCfg[ChIndx]->params.limMode) */ + + + /**************************************************************************** + Implementation of Make up gain on the limiter output + // out16 = (out32 * gain) << (7-gainexp); + // = out32 * ( gain << (7-gainexp) ); + *****************************************************************************/ +#ifdef LIM_ASM + if ((pCfg->params.limMode == 0) || (ChannelBypass == 1)) + { +#endif // make-up gain is done in asm only + + (*pLimStruct->apply_makeup_gain)(pCfg,pData,&pOutFrame,iSubFrameSize); + +#ifdef LIM_ASM + } +#endif + // Update the input frame data pointers + pInpFrameL32 += iSubFrameSize; + } /* while ( frameSize > 0 ) */ + +} + +/*====================================================================== + +FUNCTION lim_proc_delay + +DESCRIPTION Limiter processing function. +Detects zero crossing on the input signal and updates the +limiter data structure. The limiter alogorithm is based on +Pei Xiang's investigation and modification of a limiter technique +based on updating gain at the zero-crossings. + +Dan Mapes-Riordan, and W.Marshall Leach, "The Design of a digital +signal peak limiter for audio signal processing," Journal of +Audio Engineering Society, Vol. 36, No. 7/8, 1988 July/August. + +Updates the limiter gain based on local maxima peak searching +and zero-crossing locations and applies the gain on the input +data. + +DEPENDENCIES Input pointers must not be NULL. + +PARAMETERS pCfg: [in] Pointer to limiter configuration structure +pData: [in,out] Pointer to limiter data structure +iSubFrameSize: [in] FrameSize of the data buffer + +RETURN VALUE None + +SIDE EFFECTS None. + +======================================================================*/ +void lim_proc_delay ( LimCfgType *pCfg, + LimDataType *pData, int32 *in, void *out, + int16 iSubFrameSize ) +{ + int j, cur_idx; + int64 accu64, prod64; + int32 inpL32, accu32, attn32, absL32, local_max32; + int16 attnQ15, iq16, tmp16; + + for (j=0; jcurIndex; + + // Extract and store the current input data + inpL32 = pData->ptempBuffer[j]; + + // Compute the absolute magnitude of the input + absL32 = (int32)u32_abs_s32_sat(inpL32); + + /**************************************************************************** + Special case: No zero-crossings during the buffer duration. + If max delayline is reached and we have to force a zero crossing. + This is identified when the zcIndex reaches prev0xIndex. By design, + prev0xIndex is always chasing zcIndex at any zc, so when zcIndex + catches up with prev0xIndex, it means zcIndex has searched more than + "delay" times and still not finding a zc. we'll force write the current + local max to this location for it to correctly update the gain afterwards. + ****************************************************************************/ + if(cur_idx == pData->prev0xIndex) + { + // Store tracked local peak max to this location + pData->pZcBuffer[pData->prev0xIndex] = pData->localMaxPeakL32; + + // Reset local max value + pData->localMaxPeakL32 =absL32; + } + + /**************************************************************************** + Compute the limiter gain and update it at zero-crossings - start + *****************************************************************************/ + + // Update the limiter gain if zero-crossing is detected + if(pData->pZcBuffer[cur_idx] != 0) + { + // Compute maximum of gain vs. previous instantaneous gain - March 2011 Changes + // tmp16 = max(gain, 1-GRC*gainVar) + tmp16 = s16_extract_s32_h(s32_mult_s16_s16_shift_sat(pData->gainVarQ15, (int16)pCfg->params.limGc)); + tmp16 = s16_sub_s16_s16_sat(LIM_GAIN_UNITY, tmp16); + tmp16 = s16_max_s16_s16(tmp16, (int16)pData->gainQ15); + + // Buffer data x gain - Multiply 32x16 round, shift and sat in one cycle + accu32 = s32_mult_s32_s16_rnd_sat(pData->pZcBuffer[cur_idx], tmp16); + + // If the peak in the data is above the threshold, attack the gain and reduce it. + if(accu32 > pCfg->params.limThresh ) + { + // Use Q6 DSP's linear approximation division routine to lower down the MIPS + // The inverse is computed with a normalized shift factor + accu64 = dsplib_approx_divide(pCfg->params.limThresh, pData->pZcBuffer[cur_idx]); + attn32 = (int32 )accu64; // Extract the normalized inverse + iq16 = (int16 )(accu64 >> 32); // Extract the normalization shift factor + + // Shift the result to get the quotient in desired Q15 format + attnQ15 = s16_saturate_s32(s32_shl_s32_sat(attn32, iq16+15)); + + // Attack gain immediately if the peak overshoots the threshold + pData->gainVarQ15 = s16_sub_s16_s16_sat(LIM_GAIN_UNITY, attnQ15); + } + else + { + // If peak is not above the threshold, do a gain release. + // Release slowly using gain recovery coefficient (Q15 multiplication) + pData->gainVarQ15 = s16_extract_s32_h(s32_mult_s16_s16_shift_sat(pData->gainVarQ15, (int16)pCfg->params.limGc)); + } + + // Update the gain at the zero-crossings + pData->gainQ15 = s16_sub_s16_s16_sat(LIM_GAIN_UNITY, (int16)pData->gainVarQ15); + + } /* if(pData->pZcBuffer32[cur_idx] != 0) */ + + /**************************************************************************** + Implementation of Limiter gain on the input data + *****************************************************************************/ + // Gain application - Multiply and shift and round and sat (one cycle in Q6) + if (pData->gainQ15 == (int16)LIM_GAIN_UNITY) { + pData->ptempBuffer[j] = pData->pInpBuffer[cur_idx]; + } + else { + pData->ptempBuffer[j]=s32_mult_s32_s16_rnd_sat(pData->pInpBuffer[cur_idx], (int16)pData->gainQ15); + } + + /**************************************************************************** + Compute the zero-crossing locations and local maxima in the input audio - start + *****************************************************************************/ + // Store the local max peak in a variable + local_max32 = pData->localMaxPeakL32; + + // Detect zero-crossing in the data + prod64 = s64_mult_s32_s32(inpL32, pData->prevSampleL32); + + // Zero-crossing condition: sample is exactly zero or it changes sign. + // If it's exactly zero, the loop is entered twice, but it's okay. + // Update the peak detection if zero-crossing is detected + if( (prod64 <= 0) || (cur_idx == pData->prev0xIndex)) + { + // Update zero-crossing buffer to store local maximum. + pData->pZcBuffer[pData->prev0xIndex] = local_max32; + + // Update previous zero-crossing index with the current buffer index + pData->prev0xIndex = cur_idx; + local_max32 = absL32; // Reset local maximum. + + // Note that so far just prev0xIndex points to this location, + // Prepare for the next write to the location when next zc is + // reached, but nothing has been written at this location in zcBuffer yet + } + else + { + // Mark non zero-crossing reference sample as zero. + pData->pZcBuffer[cur_idx] = 0; + + // Continue searching if zero-crossing is not detected + local_max32 = s32_max_s32_s32(local_max32,absL32); + } + + // Save the local maximum in the static memory + pData->localMaxPeakL32 = local_max32; + + // Store the new input sample in the input buffer + pData->pInpBuffer[pData->curIndex] = inpL32; + + // Update the previous sample + pData->prevSampleL32 = inpL32; + + // Increment the zero-crossing index for the circular buffer + pData->curIndex += 1; + + if(pData->curIndex > (pCfg->limDelayMinusOne)) + { + pData->curIndex = 0; + } + } /* for (j=0; jptempBuffer[j]; + + // Compute the absolute magnitude of the input + absL32 = (int32)u32_abs_s32_sat(inpL32); + + // Detect zero-crossing in the data + prod64 = s64_mult_s32_s32(inpL32, pData->prevSampleL32); + + // Compute maximum of gain vs. previous instantaneous gain - March 2011 Changes + // tmp16 = max(gain, 1-GRC*gainVar) + tmp16 = s16_extract_s32_h(s32_mult_s16_s16_shift_sat(pData->gainVarQ15, (int16)pCfg->params.limGc)); + tmp16 = s16_sub_s16_s16_sat(LIM_GAIN_UNITY, tmp16); + tmp16 = s16_max_s16_s16(tmp16, (int16)pData->gainQ15); + + // absolute input x gain - Multiply 32x16 round, shift and sat in one cycle + accu32 = s32_mult_s32_s16_rnd_sat(absL32, tmp16); + + // If the peak in the data is above the threshold, attack the gain immediately. + if(accu32 > pCfg->params.limThresh ) + { + // Use Q6 DSP's linear approximation division routine to lower down the MIPS + // The inverse is computed with a normalized shift factor + accu64 = dsplib_approx_divide(pCfg->params.limThresh, absL32); + attn32 = (int32 )accu64; // Extract the normalized inverse + iq16 = (int16 )(accu64 >> 32); // Extract the normalization shift factor + + // Shift the result to get the quotient in desired Q15 format + attnQ15 = s16_saturate_s32(s32_shl_s32_sat(attn32, iq16+15)); + + // Attack gain immediately if the peak overshoots the threshold + pData->gainVarQ15 = s16_sub_s16_s16_sat(LIM_GAIN_UNITY, attnQ15); + + // Re-set the wait time index counter + pData->curIndex = 0; + } + else if((prod64 < 0)||(absL32 == 0) ||(pData->curIndex > pCfg->limWaitMinusOne)) + { + // Gain release at zero-crossings or if the wait time is exceeded. + // Release slowly using gain recovery coefficient (Q15 multiplication) + pData->gainVarQ15 = s16_extract_s32_h(s32_mult_s16_s16_shift_sat(pData->gainVarQ15, pCfg->params.limGc)); + + // Re-set the wait time index counter + pData->curIndex = 0; + } + + // Update the gain at the zero-crossings (no saturation) + pData->gainQ15 = s16_sub_s16_s16(LIM_GAIN_UNITY, (int16)pData->gainVarQ15); + + // Store previous sample in the memory + pData->prevSampleL32 = inpL32; + + // Increment the wait-time counter index in a circular buffer fashion + pData->curIndex += 1; + + + /**************************************************************************** + Implementation of Limiter gain on the input data + *****************************************************************************/ + // Gain application - Multiply and shift and round and sat (one cycle in Q6) + if (pData->gainQ15 == (int16)LIM_GAIN_UNITY) { + pData->ptempBuffer[j] = inpL32; + } + else { + pData->ptempBuffer[j]=s32_mult_s32_s16_rnd_sat(inpL32, pData->gainQ15); + } + } /* for (j=0; j output buffer */ +/* srcBuf-> input buffer */ +/* samples: size of buffer */ +/* OUTPUTS: destBuf-> output buffer */ +/* */ +/* IMPLEMENTATION NOTES: */ +/*===========================================================================*/ + +void buffer32_copy +( + int32 *destBuf, /* output buffer */ + int32 *srcBuf, /* input buffer */ + uint32 samples /* number of samples to process */ + ) +{ + uint32 i; + + for (i = 0; i < samples; i++) + { + *destBuf++ = *srcBuf++; + } +} + +/*===========================================================================*/ +/* FUNCTION : buffer32_empty */ +/* */ +/* DESCRIPTION: Set all samples of a buffer to zero. */ +/* */ +/* INPUTS: buf-> buffer to be processed */ +/* samples: size of buffer */ +/* OUTPUTS: buf-> buffer to be processed */ +/* */ +/* IMPLEMENTATION NOTES: */ +/*===========================================================================*/ + +void buffer32_empty +( + int32 *buf, /* buffer to be processed */ + uint32 samples /* number of samples in this buffer */ + ) +{ + uint32 i; + + for (i = 0; i < samples; i++) + { + *buf++ = 0; + } +} +/*====================================================================== + +FUNCTION lim_pass_data + +DESCRIPTION Pass data with just delay, no limiter processing. +During transition from limiter processing to no-limiter +processing, fade out the existing limiter gain gradually +so as to minimize sudden discontinuities. + +DEPENDENCIES Input pointers must not be NULL. + +PARAMETERS pCfg: [in] Pointer to limiter configuration structure +pData: [in,out] Pointer to limiter data structure +iSubFrameSize: [in] FrameSize of the data buffer + +RETURN VALUE None + +SIDE EFFECTS None. + +======================================================================*/ +void lim_pass_data ( LimCfgType *pCfg, + LimDataType *pData, + int16 iSubFrameSize ) +{ + int j; + int32 inp32; + int32 *buf32 = pData->ptempBuffer; + int32 gainVarQ15 = pData->gainVarQ15; + int32 gainQ15 = pData->gainQ15; + int32 curIndex = pData->curIndex; + int32 limDelayMinusOne = pCfg->limDelayMinusOne; + int32 limDelay = pCfg->params.limDelay; + + // Perform gain release to bring the gain back to unity + if(gainVarQ15 > 0) + { + // Release the gain variable gradually to smooth out discontinuities + gainVarQ15 = s16_extract_s32_h(s32_mult_s16_s16_shift_sat((int16)gainVarQ15, LIM_FADE_GRC)); + + // Update the gain once every frame + gainQ15 = s16_sub_s16_s16(LIM_GAIN_UNITY, (int16)gainVarQ15); + + for (j=0; jpInpBuffer[curIndex], (int16)gainQ15); + + // Update the input buffer + pData->pInpBuffer[curIndex] = inp32; + + // Increment the zero-crossing index for the circular buffer + curIndex += 1; + + // Increment the current Index + if(curIndex > (limDelayMinusOne)) + { + curIndex = 0; + } + } + } + else + { + // Pass the delayed buffer data to the output + for (j=0; jpInpBuffer[curIndex]; + + // Update the input buffer + pData->pInpBuffer[curIndex] = inp32; + + // Increment the zero-crossing index for the circular buffer + curIndex += 1; + + // Increment the current Index + if(curIndex > (limDelayMinusOne)) + { + curIndex = 0; + } + } + } + + // Do one-time initialization of the zero-crossing buffers and memory variables + if(pData->fadeFlag == FADE_START) + { + // Reset the zero-crossing buffer + memset(pData->pZcBuffer, 0, limDelay*sizeof(int32)); + + // Reset the previous sample + pData->prevSampleL32 = (int32 )0x7FFF; + + // Reset the local max peak + pData->localMaxPeakL32 = (int32)0x0001; + + // Reset the fade-in flag + pData->fadeFlag = FADE_STOP; + } + pData->gainVarQ15 = gainVarQ15; + pData->gainQ15 = gainQ15; + pData->curIndex = curIndex; + pCfg->limDelayMinusOne = limDelayMinusOne; + + +} + +/*====================================================================== + +FUNCTION Lim_get_lib_size + +DESCRIPTION Returns the limiter library size for a given delay,samplerata,numchannels + so that the library users can use this funtion to allocate the num of bytes + returned by this. + +PARAMETERS limDelay: [in] limiter delay in sec Q15 format + sampleRate: [in] sampleRate + numChannles: [in] num of channels + +RETURN VALUE limLibSize: [out] total library size for the above input params + +======================================================================*/ + +int32 Lim_get_lib_size(int16 limDelay,int32 sampleRate,int32 numChannels) +{ + int32 limLibSize; + int32 DelayBufSize; + int32 accu32; + + // Convert the delay and waiting time from Q15 seconds to Q0 samples + accu32 = s32_mult_s32_s16_rnd_sat(sampleRate,limDelay ); + DelayBufSize = s16_saturate_s32(accu32); + + limLibSize = align_to_8_byte(sizeof(CLimiterLib)) + + align_to_8_byte(4*DelayBufSize) // delay buf Size + + align_to_8_byte(4*DelayBufSize); // Zc Buf size + + limLibSize = limLibSize*numChannels; + + return limLibSize; +} + +/*====================================================================== + +FUNCTION Lim_set_lib_memory + +DESCRIPTION Places the library structures and buffers at the given input pointer/memory location + +PARAMETERS pLimBufPtr: [in] the start address of the malloced memory + pLimiter: [in,out] address of the first element of the array of limiter pointers, + the array members will be filled with the input memory pointer + limDelay: [in] limiter delay in sec Q15 format + sampleRate: [in] sampleRate + chIdx: [in] channel index num + +RETURN VALUE none + +======================================================================*/ + +void Lim_set_lib_memory(int8 *pLimBufPtr,CLimiterLib **pLimiter, int16 limDelay,int32 sampleRate, + int32 chIdx) +{ + int32 accu32; + int32 limDelayBufSize; + int32 totalLibSize; + int32 delayBufOffset = align_to_8_byte(sizeof(CLimiterLib)); + int32 zcBufOffset; + CLimiterLib *pLimStruct; + + // Convert the delay and waiting time from Q15 seconds to Q0 samples + accu32 = s32_mult_s32_s16_rnd_sat(sampleRate, limDelay ); + limDelayBufSize = s16_saturate_s32(accu32); + + zcBufOffset = delayBufOffset + align_to_8_byte(4*limDelayBufSize); + + totalLibSize = align_to_8_byte(4*limDelayBufSize) + align_to_8_byte(4*limDelayBufSize) + + align_to_8_byte(sizeof(CLimiterLib)); + + *pLimiter = (CLimiterLib*)(pLimBufPtr + (totalLibSize*chIdx)); + + pLimStruct = (CLimiterLib*)(*pLimiter); + + if(limDelay != 0) + { + //Init buffer pointers + pLimStruct->pDelayBuf = (int32*)(pLimBufPtr + (chIdx*totalLibSize) + delayBufOffset); + pLimStruct->pZcBuf = (int32*)(pLimBufPtr + (chIdx*totalLibSize) + zcBufOffset); + } + + pLimStruct->LimDataStruct.pInpBuffer = pLimStruct->pDelayBuf; + pLimStruct->LimDataStruct.pZcBuffer = pLimStruct->pZcBuf; + +} diff --git a/modules/processing/gain_control/limiter/lib/src/limiter24.h b/modules/processing/gain_control/limiter/lib/src/limiter24.h new file mode 100644 index 0000000..3dd18a9 --- /dev/null +++ b/modules/processing/gain_control/limiter/lib/src/limiter24.h @@ -0,0 +1,105 @@ +/* + * Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +/***************************************************************************** + * FILE NAME: limiter24.h TYPE: C-header file + * DESCRIPTION: Implements the limiter algorithm. + *****************************************************************************/ + +#ifndef _LIMITER_H +#define _LIMITER_H + +#if ((defined __hexagon__) || (defined __qdsp6__)) +#define LIM_ASM +#endif +/*---------------------------------------------------------------------------- + * Include Files + * -------------------------------------------------------------------------*/ + +#include "posal.h" + +/*---------------------------------------------------------------------------- + * Preprocessor Definitions and Constants + * -------------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------------- + * Type Declarations + * -------------------------------------------------------------------------*/ + +/** +@brief Hardcoded constants used in the Limiter processing +*/ +typedef enum +{ + LIM_MGAIN_UNITY = 0x0100, // Q7.8 (1) Unity gain value for make up gain + LIM_GAIN_UNITY = 0x7FFF, // Q15 (1) Unity gain value for limiter gain + LIM_FADE_GRC = 0x6000 // Q15 (0.7) Gain release constant for transitions +} LimConstants; + +/** +@brief Set 32bit buffer to zero +*/ +void buffer32_empty +( + int32 *buf, /* buffer to be processed */ + uint32 samples /* number of samples in this buffer */ +); + +/** +@brief Copy one 32bit buffer to another +*/ +void buffer32_copy +( + int32 *destBuf, /* output buffer */ + int32 *srcBuf, /* input buffer */ + uint32 samples /* number of samples to process */ +); + + +/** +@brief Perform limiter processing with delay. + +@param pCfg: [in] Pointer to limiter configuration structure +@param pData: [in,out] Pointer to limiter data structure +@param pIn : [in] Pointer to input data , dummy not used in func +@param pOut : [in,out] Pointer to output data , dummy not used + in func +@param iSubFrameSize: [in] Frame size for current data buffer +*/ + +void lim_proc_delay ( LimCfgType *pCfg, + LimDataType *pData, int32 *in,void *out, + int16 iSubFrameSize ); + +/** +@brief Perform limiter processing for the +delay-less implementation. + +@param pCfg: [in] Pointer to limiter configuration structure +@param pData: [in,out] Pointer to limiter data structure +@param pIn : [in] Pointer to input data , dummy not used in func +@param pOut : [in,out] Pointer to output data , dummy not used + in func +@param iSubFrameSize: [in] Frame size for current data buffer +*/ + +void lim_proc_delayless ( LimCfgType *pCfg, + LimDataType *pData, int32 *in, void *out, + int16 iSubFrameSize ); + +/** +@brief Pass data buffer without limiter processing +but with necessary delay. + +@param pCfg: [in] Pointer to limiter configuration structure +@param pData: [in,out] Pointer to limiter data structure +@param iSubFrameSize: [in] Frame size for current data buffer +*/ + +void lim_pass_data(LimCfgType *pCfg, + LimDataType *pData, + int16 iSubFrameSize); + + +#endif /* #ifndef _LIMITER_H */ diff --git a/scripts/cmake/spf.cmake b/scripts/cmake/spf.cmake index 850cf77..039e6aa 100644 --- a/scripts/cmake/spf.cmake +++ b/scripts/cmake/spf.cmake @@ -59,12 +59,72 @@ function(spf_module_sources) SPF_MODULE "OBFUSCATE" "KCONFIG;NAME;MAJOR_VER;MINOR_VER;AMDB_ITYPE;AMDB_MTYPE;AMDB_MID;AMDB_TAG;AMDB_MOD_NAME;AMDB_FMT_ID1" - "SRCS;INCLUDES;H2XML_HEADERS;QACT_MODULE_TYPE;CFLAGS" + "SRCS;INCLUDES;H2XML_HEADERS;QACT_MODULE_TYPE;CFLAGS;STATIC_LIB_PATH" # Parser Input ${ARGN} ) - if (${SPF_MODULE_KCONFIG} MATCHES "y") + if (NOT "${SPF_MODULE_STATIC_LIB_PATH}" STREQUAL "") + message(STATUS "Prebuild binary configuration for module: ${SPF_MODULE_NAME}") + set(SPF_MODULE_NAME "${SPF_MODULE_NAME}") + spf_include_directories(${SPF_MODULE_INCLUDES}) + add_library(${SPF_MODULE_NAME} STATIC IMPORTED GLOBAL) + if(IS_ABSOLUTE ${SPF_MODULE_STATIC_LIB_PATH}) + set(lib_abs_path ${SPF_MODULE_STATIC_LIB_PATH}) + else() + set(lib_abs_path ${CMAKE_CURRENT_SOURCE_DIR}/${SPF_MODULE_STATIC_LIB_PATH}) + endif() + set_target_properties(${SPF_MODULE_NAME} PROPERTIES IMPORTED_LOCATION ${lib_abs_path}) + set_property(GLOBAL APPEND PROPERTY GLOBAL_SPF_LIBS_LIST ${SPF_MODULE_NAME}) + + set(post_build_commands "") + set(json_file "${PROJECT_BINARY_DIR}/libs_cfg/${SPF_MODULE_NAME}.json") + file(WRITE ${json_file} + "[\n" + " {\n" + " \"lib_name\" : \"${SPF_MODULE_NAME}\",\n" + " \"build\" : \"STATIC_BUILD_NO_STRIP\",\n" + " \"lib_major_ver\" : ${SPF_MODULE_MAJOR_VER},\n" + " \"lib_minor_ver\" : ${SPF_MODULE_MINOR_VER},\n" + " \"amdb_info\" :\n" + " {\n" + " \"itype\" : \"${SPF_MODULE_AMDB_ITYPE}\",\n" + " \"mtype\" : \"${SPF_MODULE_AMDB_MTYPE}\",\n" + " \"mid\" : \"${SPF_MODULE_AMDB_MID}\",\n" + " \"tag\" : \"${SPF_MODULE_AMDB_TAG}\",\n" + " \"module_name\" : \"${SPF_MODULE_AMDB_MOD_NAME}\",\n" + " \"qact_module_type\" : \"${SPF_MODULE_QACT_MODULE_TYPE}\",\n" + " \"fmt_id1\" : \"${SPF_MODULE_AMDB_FMT_ID1}\"\n" + " }\n" + " }\n" + "]\n" + ) + + foreach(inc_path ${SPF_MODULE_H2XML_HEADERS}) + set(abs_path "") + get_absolute_path(${inc_path} abs_path) + + list(APPEND + post_build_commands + COMMAND + mkdir -p ${PROJECT_BINARY_DIR}/h2xml_autogen + COMMAND + ${H2XML} -conf ${H2XML_CONFIG} ${H2XML_FLAGS} -o ${PROJECT_BINARY_DIR}/h2xml_autogen ${H2XML_INCLUDES} -t spfModule ${abs_path} + ) + endforeach() + + add_custom_target(${SPF_MODULE_NAME}_h2xml) + + add_custom_command( + TARGET ${SPF_MODULE_NAME}_h2xml + POST_BUILD + ${post_build_commands} + COMMENT "Running Post Build Scripts for ${SPF_MODULE_NAME}" + VERBATIM + ) + add_dependencies(spf ${SPF_MODULE_NAME}_h2xml) + + elseif (${SPF_MODULE_KCONFIG} MATCHES "y") spf_sources(${SPF_MODULE_SRCS}) spf_include_directories(${SPF_MODULE_INCLUDES}) set(SPF_MODULE_NAME "${SPF_MODULE_NAME}")