From 417d6cc375f8d9163089c912169b8be07d9458fc Mon Sep 17 00:00:00 2001 From: Annemarie Porter Date: Tue, 22 Apr 2025 15:02:25 -0700 Subject: [PATCH 1/3] 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 | 73 + 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 | 39 + .../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/bin/arm/liblimiter.a | Bin 0 -> 79084 bytes .../gain_control/limiter/build/CMakeLists.txt | 17 + .../gain_control/limiter/inc/limiter24_api.h | 250 ++ .../gain_control/limiter/inc/limiter_api.h | 125 + .../limiter/inc/limiter_calibration_api.h | 95 + 36 files changed, 9177 insertions(+) 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/bin/arm/liblimiter.a create mode 100644 modules/processing/gain_control/limiter/build/CMakeLists.txt create mode 100644 modules/processing/gain_control/limiter/inc/limiter24_api.h create mode 100644 modules/processing/gain_control/limiter/inc/limiter_api.h create mode 100644 modules/processing/gain_control/limiter/inc/limiter_calibration_api.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f7c4db..fc02e48 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 c7e5d0c..61aaab8 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 aade517..bafaad2 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) +endif() +if(CONFIG_IIR_MBDRC) + add_subdirectory(processing/gain_control/limiter/build) + 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..54b62ec --- /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-Clear + * =========================================================================*/ + +#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 176592b..cf2d342 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..6af78ba --- /dev/null +++ b/modules/cmn/common/utils/inc/audio_apiir_df1_opt.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + * SPDX-License-Identifier: BSD-3-Clause-Clear + */ + +#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..781beb2 --- /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-Clear + */ + +/*****************************************************************************] +[* 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..3c99a27 --- /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-Clear +]] +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..09e4231 --- /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-Clear +-----------------------------------------------------------------------*/ + +#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..11d1964 --- /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-Clear +-------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------ + * 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..d0a6cd2 --- /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-Clear +-----------------------------------------------------------------------*/ + +#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..752dd35 --- /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-Clear +-----------------------------------------------------------------------*/ + +#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..11ec936 --- /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-Clear +============================================================================*/ + +/*---------------------------------------------------------------------------- + * 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..42194a0 --- /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-Clear +============================================================================*/ + +/*---------------------------------------------------------------------------- + * 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..051b535 --- /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-Clear +============================================================================*/ +/*============================================================================ + + $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..f02ad93 --- /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-Clear +============================================================================*/ + +/*---------------------------------------------------------------------------- + * 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..e05ae61 --- /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-Clear +============================================================================*/ + +/*---------------------------------------------------------------------------- + * 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..acd684d --- /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-Clear +============================================================================*/ + +/*---------------------------------------------------------------------------- + * 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..f6f96ac --- /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-Clear + ========================================================================== */ + +/* ========================================================================= + 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..293a974 --- /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-Clear +============================================================================*/ + +/*---------------------------------------------------------------------------- + * 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..7798251 --- /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-Clear +=========================================================================*/ +/*======================================================================== + 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..a0dfc3d --- /dev/null +++ b/modules/processing/gain_control/iir_mbdrc/build/CMakeLists.txt @@ -0,0 +1,39 @@ +#[[ + @file CMakeLists.txt + + @brief + + @copyright + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause-Clear + + // 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/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..904a321 --- /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-Clear + * =====================================================================*/ + +/** + * @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..82f918a --- /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-Clear + ========================================================================== */ + +#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..1a84df9 --- /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-Clear + ========================================================================== */ + +#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..d2eab90 --- /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-Clear + ========================================================================== */ + +#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..fe0d230 --- /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-Clear + ========================================================================== */ + +#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..14d1f5a --- /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-Clear + ========================================================================== */ + +#ifdef __qdsp6__ + +#define IIR_MBDRC_SPLITFILTER32_LOOP_ASM +#define IIRDF1_32_1STORDER_ASM + +#endif + + + + + diff --git a/modules/processing/gain_control/limiter/bin/arm/liblimiter.a b/modules/processing/gain_control/limiter/bin/arm/liblimiter.a new file mode 100644 index 0000000000000000000000000000000000000000..124c6d3209f6f8580c223d90ace7749626ce8bc4 GIT binary patch literal 79084 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ϗho!Ng5T zlHy2GXA(k7?So;Oe{Q>wf^S-@sL?jpM=ZXL*g!=##Bby1+v1;yH|8%=iw%32qz;Qw z*)1Gq|8RH%IE29}5FJK{c{%J9HS81&`$W9$Pjjj30hepST65xB(|p@?z^OZOhf4E^ zb%ybEhI&i`!{=Rqo~Js~z;{_Bpk*F(lYXNOolg2M5+fKPY&_7*`o*>L77UxE*){mo*i+2jWe2{nY7CU*khX?dGB#L z={Du0+c4@mKWNYSzw}F5X@v>qvV=7WAX<~KE;e&wrlf>I`O=3vPnbI*KZ=BYA4PUX z>a7VO^?>h;gwr}@CPbD}nGY3}z&0%csU*@{^Si43u*vBDG53)({Ms4$p2t|dbdmX< z=a2^@9`YQImhkcDlhJ@rMlcuGRuFXABdt|vGdOt=(!~W>w(QHtotfCtHn0vghd7U)1*ZccW{ZQ25 zC>yUR6?tdmA!xIFk$KGX7i#kt52XjJ_@bhuU%p7nZXSwS6Ak|m8QRs$aZh?qd(d#& zbHs~i+Yxm#;lBiuoRDz z&GAYY=VKJ<8JK$eB6L1ToexUK0+w1S8?c?dBnNX(V+uG61p7R_C)QE26BJ&C(%HD61_YI&~hp;Nl9|6 zvhS@b15z4(H~{`h=IHmH=Yj{t1QN7~#A$dQG%NaTv;+nrNp-SOFKs4S}=EgCzMDCNByZpMW?=rwKz@ z=}^=;MpJwA((dzDwSE$HCJG{-0g)U+l1)}Da0|ddP`n+j57^>Vp{x}d8HLA6YrWfn6-1NfVYA=h5+LFTYa_?zRHSS_!$9C?O0DZ*$p&RHArGiL_NKy|Wtg+3v-G^d3Btn9CflA%1 z7~iZCFqJ|AmSctP>k3%q?Fh_bX^aU<+FKQoI)yn_m#S^2;68+1x;tc3V5m*dr0=ZGWS17%-#F9i=oIe8j*a|&F)Q5dQw zo2Z;@dcn6HhZYcTkAt4dfP~2(A4g^|-RFiY_PLKSV2-&zb;AOm{Zs!a4$e0?P&F)v z+kCq)HY$63ufu=l>%QOm!0flaMV>Vk=2yOpq`Zh$Pm{PKoi~lJW!jr?h#S#Eh}bZ6 zXn9bFK?H?748nNo=!c3r?7@6;^Wy~8lQ=ta-t3L(WOnI{*%~YNyIW&7aCG)Yu_OLP zb@njN#dgGYO$KvU?4B?X%Iu9f$Hn(p?CDr1{?EleOan+(Ve!8==80Ii?P(4)MYUpU zffH%yj4jz+jKQhk#n_j?&U}fqxEDjA)NK;l-J}k9IH6QVOevL7Q7X%sWUlc2C+x3g z36+2^I$|%xhPEl@!Pvtb<8bT;Xbblzl{(GE@?6Y%E`sZQkiymn%WVA4cp!JmqjXp# zm=|N($*(;YYTg90UGXS(Q9t_-C&vJ9wBKyL6}y+zd#T~gO2eDcX%EgQU6Fn<=4oZJ z7s>L)*k4nNZYGlnXK&0wZX*uD74r1)MC=+LV9HHbOY`yOk@_g}O|s30d92KbiZUN2 zv<%a8u`5DqD>s^*u@@=9lW{K~>4Z#XZZ*C#pvzarUK3*OHQzM>y=xvbc{X?M)~Njk z&aqE`nqqxrw3`Toc5|6&v|@35-h3G_xSGq&=S(cIs7czUumgaY0@hPDYv{OH+k-c5 z%stvZKvEd|1rbP>v`-7aCjc%p>+E@EPOkyMCJp^5z8QtCU2z43F9V4#V~XCCU{3iD zoIK2jwY3^c4hC$O5z{hU*cXY6EoMf3L{q{#teL3i56otL==Dwb50b()tVph#to4yK zkZjhrYyXN!4r8Vbgs>zJ+1kwSi)EniKAkk?GY zI3&0CLFSMk46{Sqh5yE?gqy!Y(+A-sqrJ^CghMx?&`tTnXGIm)XcH z35IZaXg zc-t_K7#{)2J8kmzn7lcLdsmy|<^4Eeo_BraGMBoSyK#eJLp*Lvyp@2v51mQET;g8p zHkZ3sxXta6&qU&|yDdiUPUXfDPd}p$Md2iPF3P+R)fr`e6SXwjTpqnD+HPLsUgtI+ zb#Hc?+uYwq$X$foL2x}h=`+t@*|XtoSjuElGGC3~7cZ~`or^{8UZIdk4|rrHA5;nc z&n`n;3$y9k|HMS&m?W1u*h?#JYiTZSX|5`+u9?v`tGKeQx~{Rgrn0KGu%=;FT|-TA zX;DeRth(0XxsBCr_>qEQ`=&owYDe>2dQkHTiDb%XF;K)ES%NQ zR#Q2nu6R~e)jw(~y`t!fq7rf^yMIr@%BDK7t!QhltG8wN|7|pg_3w$)(lisgwAM7w ztgM1#l!J<^AnE@vJ{47S8;W`TTe3NkB0D$Eo!i(@EY0@sartLqQQXqni~y|1|AJP( z>rqt2%-Ri*k%V8nzCl}*SLbFW2-*0KI*nl-K&l;U?4a~4i zSFttPm1w1Aq6(x-v)rZDXhJ2XVI`+oW&+p@!HsqeaHmcgZkXG^?1>|1J$6uPNpT7DJ*aE~+=P#_E|QTbLM*@CWl9NTDxC<%#|7qmA?VUQd&lT83a#S%A| zUt`ffCPyiB$G=c0retb>l#*jII0js>`+lr&#?Ka^uRlSVJtnV+tfxY}Vk-v2Y2&OI zV)~&SJvz}NCFCY6eY~ahv64zbo@=FpJb|>uRw|2ZU~?2kv+`U6CiSzTjMl->CD+2c ze^#CWKf_h=MgvM$sXr0k_v2?zENLfc}kTweXM*2Y_63iO^}0NF#MY3G3FM5I(BNY z6`;|LmLjVMDrEH8QRqY(*Oz?}g(Ms9YT6PffSt zdXPn2F6)RcnKz0Pte#-t&UC@9e%U`qayKH!S}7PqLIEop872wL`=Qae$x6y2Q4!GN ztPG$fRUa#j*dB_=)kk5K<^d=2E^78%KrdD6%K@)_Vtsi!-6SFs1)Q z7|Jjb4$eC*ST*mkB z(m?ot8L)iD*LN48{-qT_^CURHc>QiR?nGmZvGZ;y-P4LIu`$uEQsZZ6m)?TI{0W%x zRstra45yw}0&|?q8L%3I3X@PaY4DzLZNM60Tp6%>8rKiDyhiz0)PTdg-pn4xuNM_r zC9X7#JD>4VinOj-8N)4K9+K>QlJtP|2%=2c>O(823@?Hd)t(5dOe@M|L=~yb?g59s zXh3f3TL6;M1S?f`F%mH+RV{z8^iL*Sp&O54{3PSFkh5?sQB(0{K+68o^=1af>kk_t z6#JoCf+}o9HyW!J4YuNp4iUhA)sV?=)gm~9O)9KHIoY@%I9D)_0WS&qbw1QafJuLhM50kC+`^sUA$b(3Dn1J?wrPnMOC+x6|iKnX6*z@HIrlab`BF1kGDpauKIT+|1ZE=E_mIoXUhopMeD z7VDmuQ6yVbK##SO3$5rJYoPIyMMcG+;oQrWLr$~YiCBXvkCkaeBWD^dW3gFcHjQ>& z?@z>1mu$SVkkcQ_-3XWQbG##ISZ-rKatx9*L`Ga-Y)*Z-$+CP>RDQ`xMT=lCZVrBd z7>9)D9|N9)!O#O7SKk=px8Nsx;=cevZL~2vgmw7sLLxoJuVB(>X{&JaF%*pJu>pl3 zgP>|Q;OneGbX!J$YY-MZR}S)$+jxTermz&{PEc!|&xz*CAsBevgXyk=4w=ckf-!%O z{V%;a#BaG%=yte9YG7p;3yNU#y8^{UfkT`}#&JxM99MEW>cvP0Inrp@6{$*&gRo1P z9iZt6q?%Xnky6W9ig}uV8EMbeUdZ>zliS*hbE>l0HpW0WpyU3%6cP;RAMx)3+25LPTI5FLotiUStWAc zP%D{=gwD z^1&2ouvl?{UDd2~V5EzD(2qwd+0$q$veFG-z+u^B%b@g|37Z=JBFl&Tn_x?uXys-C zJyd~Mg~4u=5eJDmYkin41F%~fM+vzuVxQx~VRirp%w%(^Rg9{V53Z7{q|rE7bwEmx z7Eq&O7mjT;Hab+q7+ZvW*;N75#>km55c_S@$k6CXc`yN6&Q)eYm_Az)q?R1ruaRs{+DH?SUz_wd9y3!pc z@h)QtOy;(Y_Sn3 z`DUa9rb3?Vx1Gg82U`yt>=L-Iu&~Bah&) zFr-h!ZHsHHL=Y%TxM2xLQQepi6?-c+SeKo9OZBh=!b&Z5M{4BBjdSRqsmMQ4i@i8z zPEo*09$=YQLb(=HSz>o#VvD9eOw9mu?6Nqq7{X5p@ctZ_}qaTM=`uyA93W`Yj)gfNU9B3E0zOJ$BljJZM22HAaz!sgpClHqVN zd&`?Ha%{?+Hw342EH%dXfYCo-+{o`GIO9Yw`QIC`_+J<>CuG1>7*O48!7d>H|Im@$ zC&a&JFKKp1x4y1IRn4)YuyMjMkTzqK(PrG848e}WZ}qwbi2RTd@fdHg)!T?Hx~w6M zc~cdh2S83sMC81%h{Umgcd`E7pw|bCfdOMIziCj<65dVHcmlePu0;s)MkG@PA{2}5 zG0}>Aq`6A`Ru&lg0s%O^oSP}1YYj3_6mMJc5bu@u_0Z!%oEv2i`{*HejKExh9@JH` zapfSbfSAWu$sGq)G+Qb8mM;S+6h!lQjam*22I4HWKZht>mMKc9G?y>ns+FP`se$Vt zPA{bRjgUe!{vu%UWE}4a;CRpEA8G+(V>^XSum_X*3o|H?0R+}Q&V)paHP{+qLx&xlZXo!C!lCYHbz`ZF4JX>sngz6X)_L z;_>V2V{RBTrF>Gw6Q*IeQX=KId;p2j$f{)Uq z8Jx#hHf^hrc7=>igZu@1nt5J-QQz1wD1&hvVsHqNA)mAp!kI&@8Gg`K?Y7Xi( zGll(-tHPGMsj|6pZc7-SHayLfT*z?T_!~!c$p{Smw8ErFX)-)p` zYUVbzE(pm+E{$!iDkR_)`bo&3scdb-6TBLlQYc?ZOVi~t3SLJHlXz z>c+Mi_5a2LX6K3Nzk0SlqphyKTC_khJcX=8h+j@w(IUhBrW=M&tw3x#=3p;rOerQb zu-jz>RKPlLF{cPD1O*iPiq_iZnwHwe`f75LA%GZzAMsogk2vcat16`8Rfr#i_bfd2 z?>70`I02c*(5!Z8+EVyYk}rGCmXDhX6owILulEA9Ey zS}7AkC`Kyf6WFT8Ha?mct(DCTM42+~=zkTmM`=YPp5j)NmXwsp=z)EkTLaw$am;^* zJuTscn&x>m)tvt!t6}y!;yN=<+%V=wdP*g|dX@Z;12EsJyL=h4lXN}a>1e6AtIC-K zFbwnDM2Jc*U3nCaZ*HiTDWyfJ$k`!@DyxB~tI;_u!3gQtin?mSN874Xi3pR zHb_`}(n=~mTkGc5@L6{Y0u46r#+{?I%?OR=r0g5YZz!KMY5XMNjN*iXif*XqWCA+P zF`I)!9i2)HPjfj64i<~+#NF1(B&2{sbH;{UjB1me~ zqoo!OLFjPafput=xZG?*dvi^FO=SxWf&PwslgcOGG;RuWCqjZt9Hs?d!a-bAFuLS4 zoLOHvtD>p7W*##$24wWK{_kH2`QIiZzJtSeU~Ikd3dX1#Zwibky(*yIvk4T=ZLMva zI|Jp+rnc)EY8o2~=gn-IH-L!x#>&=0ysmQ{Eek{yD5@|IYUUSKUd5jEErr!}mGw1M zt=Hkr9$+e)t7@;qJ3sTTnpabR*BsDmR-kZve|622v$YUzZ$+2J=0dz7!lkEhW)0sL+!ik4c8sI5kivGER!o-fSlpmia>wiT3ejz@ zg#xW>4MtEnDOFUpHDhwj7ZqiVjrH^uf*acnXMK~aBto>Ms%Zg|72}O*si~%NjyKqmCLVF_JMeda)CR%7;KGhr`? z=-5()*^iw9*NwZXWZ_r>$*qO&y|ha1a%!kq=*-OuJGEBT&cV7-SKocjU=p#7iEegs zmBG#JQbl!TYh~Bm;j1^o*~Q&i*HB;6pmc;xG_50(oep5+rg7k3+0<0O0M-m;B1a#z z$<2zrfWt%0H4UvukJJ#etiG*ANWXABQw9@q$6&u=qnvmLd@9#cxmb=EK4Nrv#qe=s zM%{Qr`HfT5gtce4Xq+)ydJuD%nJJX-m@nWtx#Y^QnAL=hS!KhlnhH*D#iY7!=1gT7 z#02vqg{0atC#X$Tc)qu7art>7YFIip3z0u~c^ontT-XCN%? zB{H;i=-L}7PcU9(2l$|x!C1qYToJyS!z(a+-5vO{w_;cqWC{j-bxRW$Tez&baejre zv)XcUt#O90s+b$LD|d9V=N%?x*hXIl>kY%NFTZKRsNtA6<8K@{O=U{x&5e1tjB4-F zB|dBeQi*K<#I35ZcY&o>ifV*ZXe;ilTl6F7U_Zv4SAw+ZjG^Si*9q>>blJ})io`qWQ1#q>n7JE*JRfO*Tk5V5&M4o<{PiS#=i(P+8&3m z?Ce3kEGBSY#8UVDu4UE(=5pgfj5V_RSo)X{qrj+rzk4ghO6pE_e5(b2XiD@WfKNMM z{_aRwO#afy`y-aQA8;+V9yC`N4}tsenDv@I&J&Yg<_$<{5&G2^$g(uze)lrh1J-i$ zL1P7|@iu_^I6usDIT~kN-lO9WNnLi_@|Zz-n;bSIr}Oh?|B(J}ML*7#!TABZvY3>m zuKTTJ<^#rZ$XJe>H4`GNiS9~|ZEkNvs8)8FniC_u8So5vm&N$*GnX3oQ{Um&08z=8 zW`$OQs&!T2aN+W}hev3Uy2pyrigB!?mJ`;_mVCfp<6^wB9e*ehf5;YJ@}uat zhQWov{|N9Nln(s_#ESYT`sraX`PEVg`BClsg&+rJJAcW^fhiw->cCPS2EF{K{`8>( zvz@R&>B zX_vtA_dM+Z$^YI<+E-r!GnSnX>0i18e)kgi=q2!3zzY!1&FD-0@n7*nAM%U41kM3` z7W7MO`ayv2uyY>!kGX_?(k1YJTmsiz0ykX((>KnC^8DZu_@{vH#eC!6Ugr3(0RB(J zBY)(R@b537|1;p<*g22w&Q1`gl*`{>%5SyLN%`Gxu<)i@_nj}cLzh1lbDz|85|*2* zzdK8CgV)s?J?z^ftcCX1*!6_A@}av`m)SQt+Z<8NR?u=70YTWoT-1r(F3{Ebe zQZZ$C*|_ow?C;ecmPeskZ8!>tWOr`qgbUGi3-8k7M!59qCJN>d{|mDxPly>Tv4GCB z;Z-bq2=S;5f5fsE{tPzD-a?$VVGpK_2MKr*%RWL>+a-U=*Mp5vCrd80Sk65~`2Suk zi}CkOSppxl@i($80Uws&zn^8P5XV@8F2z)3F3bLy(=0*HpGP5m8Otm1gGns&@Eag3 z(Juo3;ezz|8^MyEKV?Mvo-9E>gC)kPXE{KKRW|$r%PWQ0$r5_+VTpcku^fnBRk7iN zEU&_EEU^Urc^l@h1d;wC%N`itg%a}cXJ9E$E=$PYlO^b`VF@`Vu^fayea#Z`9klWD zv7CFbKrUekKKwyW@>$0ceE1t3?{L2#bt5|~HHkPm(f8m|-=3-kxzw;OU*pI)r#(o=EqWy7} z*i-TcGzjYl!xDTuZ1^I}p17iMjlf@3w#xx_Inplc zS&mfu&eU@J-zC6lL7ZVw5`LQPLl6fnG5*^uG5%*PIW9|%Yr_{=a$KyN9Jj~KJ`P1gRM`+F84eLgie=&y zv0A)Fyji?QTrX}EpA)x;uZVlaL*g-!7dxy^3hGH_ip@oyqfl>_&j5!=zC@fUE)-XY ztHgW7$Hb?_=f$1kTjB@er{YPm3EGx+Xb z0oN-xQ_K;I#B;?V;>F@LaiO?U{H?fNd_sIt{EN6({7^hD{vdMMceY0}F<&ee&lN8a z$B7l<9C5LDqj;D2p!l@Nd6MP5Eq)+=DxMSr0WV&Lh{xAVx&o2&A@dn3{z|MAXNU{M z72<8;I&qiS0`na6>mqUyZORvjzY?p(MIz@}##;*H`%;&zeiSTWr(F@*V%a=v(;I63@{$!~DtcAc=CDNNy^&l)i)HE+XHj&-iCaK3D7~{V>U+ z#EYb#BzdaHm3W!YJjn~i#U$$Svclhxd_eLiB<$w(Ecu1>KS;JQA5$M7k$<}6ERp90 z)VG)1S>*Wv^=C>xM=X_osN|92h0>Qxt`Mh5Un_ZmxJdfzB>zTSMPjmiK=Q-l<0R~P zPV!6Qb`tILs<=<&`3UtcCS|$q$J< zZ(%-vkoG5tm&zjVo2VhicpOYSTdN`I#0bHq~Vhe{qP zUMPLJCcheSByzNQSubAO8WWY zHPT-z-YETlNxoZLC;jgwKOt_Ceyij^iMynKTQV0CWRS){X(*x>B$ii?GhEUq|cF@FLsu`r{vz^K=A@`1c`EfCAmzT zBK;ik*V11tu8{s#$-fm>lc?w8;x3VkH&g#Fk;^ty4rY1G5xIIZ?J1EwT=E1G^}bY` zEd5N$bHpp8Un+Tpc!Ts@&6oMC7S~Guh~&q`r={N_dAsrdSo~fLP`v?P(Pt(Y%;q1ay>OrkzjB;wByuaJI;DFw-3szY^=j0P4u_u$Un>6S?3p!}G-gu~dMdzVug1UM}83BHbP0@1=i2+$_E*zASRFW9IvoxL4dqBERF} zNihZYeyDFM_7n$;Q^XnK^&*#qWV(&wPVo)#h#0^a#dt;Hnc`5fN}MBdNk_)NS-eyH zlXy`4T>P3uUGUU}sdq?hO+wG*8rdFPx{*9h>?M|n{l%f;NO6LAsd$-qxmY947q1qV zi#Lg@#Jk0{;s(*I)d>5Zk^G#vUEC@DP24YXrA5|@t1gmtB--ys67|n$Fx1y8d*%cI{Vy4(y%oV$cg<>!9Y_Y$1zBp1G zD_$y25-%5LiAzcJ(@GNUc8~P;i4RKugyg5i=Sie{SNvS86C=4^c$U~%JV%@$)`~ZY z|0O;x?iBZtI5rQ9Uy478$?d%GW@3SOrZ_+xC0-&{iM8Ty#Jj`?#3#j<#BW49&nq`Y zY%X>e&lU%Yqs21ua&f76lek)ZP<%?tXQ^ z;tS$7634+Sl3y3!mVUqFe~2GR|BaZ`5$SQ9w-O7*QgH+cd!~q2ktpwK$+t*eO~TH5 z#mB@a#b-&R+a`I3_!sHlmi$+7pY$I|J|ccD{Yl9`h`vr<{^=y_ZAPMA9Z1;KMLbRH zCH5f^Z-C@M;usS7Oi*~0^fSbH;zDsTiF{T{zES)y68YSt@JFTJC_X2?ByJ~>&#RK( z5ciVE=L7L$@r3Ah_QF$1v~z~!c9Of1Xon*4T(MLfNFv=x$z#O{(od0GDb65a&l2%= z@gDIZ68SwP`HzxcC1J-K;ycnGkbFoyEd2?|UyI+8D7Qryk9lGrF-D@?a`7tZZzfUh zZQ>eno%pc$nE0&tyts`-zB|N#1sT_jxZPyzF~1kVt{r|)=Kl(4=c|ZFeif9g>&(Y> z{r0uaV{zNS@?oxoi6(xLpRQ9{wL+h=)OHtU~jL z|H4UlQnHXr^YjM(jx%)@g80f>{5O3PpLLOhf22>xpS>H!#v_FI<2+Pef;o6|=&4cg z4%v!$IBdY$=6G=_{`zkf&cqWXh4>>jo+!b;%@d87iYF71sPCwuh4`=ioT^E+H7Mji z|KN0vU0~5K`00(Un$Dq5l8Ej$|L81{eAR9E4c^7|J7lKEuK8^p(~OZFke>|+c~NFu ze!5~~-v3)X`cXU$c1?bNfiK5D@i6*b2wRB)B6ng3=lZk{E?sY53HIY@uxrge8~;h9 z@%qHWhCz<+Gw8^9_%9F6;p3lp*d!?8`wSa$sb%GGN1u2Y)Q#W6hN^I3BP{__wQz<60C5C8b*J;d9HPRN2H4;%XgWw4z=` zo@Qcr@qi&Q+m7eNx^$|q4~rR7V+zANjri$gHM1~i<5TcS-SA+{ns!UEA#8H0yYpkL0$gbU>Y( z_Su`EqhV)PomKSG$8|S{r;Uz{jGZ52c%q)=tx{ROX?Mn+LRrmGR{H)xY-lWWv|rH5 zXIZl=v&XxpB<9e*&X@dB=@%&BeCHLFV0HcX{^V&7pYZvdqbse^b#BW$OWW1?+Ma9_ ztKgI4!>`%+(#fJ6t4qn38V&AqF16#jVKZW`Vk zzbf<;OSD&))`hIzv3EZ4-D>1cvrky|=B?#b&ij+99x0vqX6M+X%F?>rTPn)iRfSL3 z;k?QRC-$prSDjb&eqB<|;fa^DvhMzE>8`C-`_%*5mrvW%Zrbo8*M~;cb*UbHWO-6= z(=wB)0<+WsZ68)8=bz(MqCj(z&r0ch8WWqT;09nc=_Cl;Buc)uY?ziHMW)J6c*|$L!NR%f=4q55HLZ zCVe~_%QY4xwshwI($Wp3Z#~@5YM&mqIuGB!?elair;8JdZW?~Pm7D&ZYnPn7Irzb6 zd0jvFEdT86tpmQfGqcm4Xcy;1B+_h8tJ7{M&5jK@d2^R!>G~mda)SQLH!rDkFR*f| z2Qp;YM5Ob1sY;RRtNPI4Us?UPeR0!2(K6!b(hc}(<5_MvE%e{7{Hh0>llbt3`TyAv^ocQT9JhW5vyNNmF!;jc`6Vb}a z@aK%UKldxIiWcnn#LAdF?Zb)9_v94B_I&f)w$GzQAADBab=&77td~CZ-Qb=mvfLBr z+wO_p4qCQX-;+0Yv~p1A@YIUxm#o6l{hbDNx?n;7EBo*Gq(z2%;$nv>KKLxRYu?G5 zuVpSw$s90%x3{3nJ{Ar4h%q1LQc=;__(Shw(c~U6Z=dC&-H>}^f0IEO^|`-sn6{f^ zrr)~5se@AML)IUT3`(h&?#A)-#Fy3*G`029V*IuOa-QvvErzWJHje4tuoYsvp)!gd z=~wYTY1q<_Gu&GqpM3x>fGUV#v`i-!ppWcfAV_E$~CD5atyam zWGB%Nv*A3Xb)yjVyGs%_H?5-tyl95}V^1mRb;70Zo$FkvHZR=~9Ie=}o z)Qk}$O>O+QScSXmqU-MY%)Q~j{@ry+w{R9YtMbj*?z+gvr(yw&z4@s7yLHw^rah}N zbixjy^yJdI{EhL_-yCohrSJd5cemYj%bWJyR_^@D)n*j`;4?Qlh&JU>=*zou(i7-= z&gkrSw!yU zD}v)<8wOh4+U$R@bi>vTvFA_NY4;xuxcN`LkMVR&5%cQzq;t2`^}{_qkYb!^m;HyG z)yRL{s+iT~uX|R{#@f z>{w>KcLwvEqf4xu<3XH@Ru{S7S;wRB8~WVRjoGo(Sb8k0K5k&OiS6Va4?4q-^ovAJ zR7ZT@MZR~BMtnUcp7Wd!$M9sd=JwAb$-a%%FIAt08204;yD?H+ee9MC_J3w2GtR`% z?@MB<$KzRBdtj3ATQ*PitfGr?mC(8nSG$LGg4L9#e)vR^g6BXLafAHNXT%r03)d~d zRtWL$ZB_uJ^Q+kP^V#-@ozx4#JzVT;#5EOCEkUZ_pAaR`K5HHju><+YFZemmA_E;; z@Vq$U^B+XyAphJK=+uhGda#E3oilm;9K4-TyEJ7v!7Gq9(7oF>>_?LPTy7?qinM{E z!VXlq{;O$hlG~M5Zsu2ZlW9!&Bfpww{F>On(pqG2kO{6tJ%cSXHeo-N*_{^Te_E58 zXW~rP3LZp@B(=!gfc^A57UoAgCAG}_GZYaQuc9@D9c0KnSQ(nujIq*(QLz(&q1ny& z*jjoS6_fB4TAX!~nN?G97RnB-$o`B9+8z8iiVpoI`yWsQ|HkCEH|+_1I{$tgJRe1e z?rx10Akc`u^$70C#|NB@X5ATVIwRa68D@lUg5C;`K-b#gCMd)gz6IZSeHZd~!_yGz z56?vgf$%>NJ!pApdvs>n`%q;o{5Qn6!%Y$2r_esL8QKj-SmD?3Wry!){-=M!_)G5c zXWj%p6qr&a^ZMBXfYW9#f5y5SX;9+65g5{ouQihv-*PVoz zBg}ph7BIw(EwdkS+nRhBKk9CUesq`(#>K91;&1kwTETK!c%ef>TlayX-F{Wf3gf)j zx-`ivYZ~$?OXj}oz6Tp7n*Ah`f4SL@xQh^flG%^Ce?!bEO=xna75p_CB|O#H&-~jm z|LMD#|39Ig8R4$w{u*Jk%)aa59gSACDL2V|0D-fW|3KtFNBQ$2Yp&VPbopn~8nd76 zE`go3CT%M>4{_#&n17CY55BK3`h2X?h#!j0ekUuHRSJ%$feV~-5b2DWs9kkZ_^N1M z?BrD-WKjy60|O7iNu8UkoU1IXS*nJCSUTyX#x+RFe)Z{(I9f&yBg!otLO7}7B zFAMQ8py^irM8Gj$72KnR&IFX;uy3qXmS&1cr3v}hHI8jWFCx>V&O&gnN6|Fl6}|&V znPi+Yx!oV3&wLs&;8eWr_0!_a-E_H$R-3O8^ik$jRASB2HZMawk~P5-f9kyznM976 z;3fFXZ!-oVUlCdOIEnPI9I zxMazqw(ldGCZ1@Oq+kZKW;@BOQ5Af{w1L+UDQ#fy-(AzQ%NWGIGGA8v_Anx4e~1V5 zS_W}&Fke>68R(AfuF3HEQ!>0B(Xa5zdiRu|nK1I0m+EM|EGy*^RK(|55J0wx#_d9r zm%9-TS*;jkj+2y|VMoAk3hzPf69~3a9>(`%zp2Ri4cbr8?t|Q9MwuMfs^%}E<}W%X zXi+?K)qD%&)Yg1>$NC+EjN?$k8{P3`?pmbSFmAkO#&hxXR!T1v81ibv2e`cUG_I6& zC4x>k8<1=!va;8tV9&n_ZF0hC%Y7biyszV@$DVL{^TDh<$Ze_Pz;ePlpE>o0%o9)l zFm%uf=OVVsuOMGU-8g)G;iOP^1$KGE(f23B`^qs#3{LCJ!_vN!X!Wl>-R;n=V!R(< z@Hd`rBjoki&hE#6e|ySmUq@J5=Lv05LKZgPrHFCTNoxiJ%OP_*X?huMZ)}0WUWdS+ zoOXxM)=BfBq`OvOvqH}!*t8p?*&{+&k#;);jQt?Cb<~YVHoKj3srw7Im$CWg zAcHr&svUs*9(6_d+T-cI!>)N!hK>2Ar{jDU!j{A8u3|m-6^1?UIjQer*Hk$RF*2}O zAx?d)@(+yaRoRYLnf1pidObIw=FG*^JnJ^JTe)*T((o?4y=FBs_wR;S?zH3JR*Zbx zWAopHuSrf8J?9GS&Z2G%;!So^*%Ip@^He2!npNQhc;<2#5+@5^1ofHf)h7eJYo4-M ziIkT)X1MJJT{~<(&H$BOya|xUP{)~|%F``{JfAwwAk#eEA0R(N9cQ!4J>4H6bM*7k zp{IMgS0VoeTh1TQj!RHwn_oN>9&wxzsB+Rl1aRQphb`$H2w!671chA?e#FlA*qZQ> zja-~M!iDT2tezOS^~J@39{FhcRtVvkQ-RzN>V&O{Rv-sTj!~g**cpbM>rChfC;Zc5 zdf`H3c`<_6XO!o`qK=>AtHgN)G5g?wYRgyZWN>oHor@ z`5;kqykQ5r0yE;l@}HnHj`SCwaU`Bvw8RESdevtfiH{K#Ve>tRY+myvaj3llaxJzN zemKo*z9@BO6x!`GqtHr(aklaGK^1oUqC61qf&5?8eTx>~?aQX_ImqVmqR)}fZeI>{ ze7xul#(Noce%+Tu_x>m3L)cmzL4L3M%n7TH_eWm$HDfp*&E;cAO_#%4VzATUMZz&( zDxZ~Txem&tJMhJ`^(1UjhNe9VO*csS*x)1Aqd3k_43eG`0*=ycOll$cQ z^5{N%_-ZsZ<38x&iWX>gZY>J=b~+GA}WF5!AAyuM^$vAY?u{ZQQMs z=Wahi{sEhDw@wM}*2!}>PUb;u#@+TI69;{Z_l>04Xx#>P>x?LQ*rE(I?#8k6JZx|Q z+|@CC8|s7|`dAyJrGH>n4=sX@zSyHd7q}lf&+o9~`5kuH+vB}h@;fN$cL6>YO6Lju z9Xs@x!1vgpn}h=JIwo9|kTAr@L#ccyQP~Mfo>B5$8Z>SJzRfrp;IFyH9R8e3bF8eJ zkoOvU0~-{gz2;KvpcV=XJYg76#gDNlTLIupb00kiWy`<3zaLHX}tQf}_}c93p>br_#r}LGC~uC(--u zH2OFny1Rh7wa`6ahgil!$TigMgb@$gW_r8>GOu6H%R&`>57|jH+IQ;+UuR^p!7Fn! z!XITiUO+xUx=h=Ql41(%vqKd_zFSvaI5q8F!(9frvy8|Bzu#&0C^kY8&t7^&zP$ZJ{NhEDqg2PYgDZ<=Qqce!9i)Waey8W z3#noYK4<)J0K~pju}_{iZa5C&7;Gh+xcDQeuN#W?{WW7We%UqcG7F)6^3k-*GVCnD z7G*5cE_Xw^4I7%4ha0PH_l1;g`$|yXq*^F0MnSL;=U?Otl1CM<8 zF`38Uuo51cuS48P6^EJOQQn_83XzXKCUbNfp2GnuDHT~_Yc~xq6B&YiKFZhuTgHR1 zdbBw;J0D_m>b6X zh1eg>vt_I6ld!C7HTJmKYbL>MP0hNRnyo@9eEiYWtgETn8xa3Y6>HYb)Qk^29;J#k z>t<@kXCJ?V3NZzWjf zY_=PAEMJGL0>I`WR)ANvL(zL61G@2=!nwSex$nVClY;pWyHjk%mmoNopY<8``!2AY z8KJkCqVxnh);-haB;b686s=NGD=Au4L5qEMOsg1USpW=KLn>i%+VTZ4S%i$q23Mp}^<6SV}uw2CSJS;PCLm?TPIV>k(=TdCu z0IJ2#Y-~*~MKWD5;Sgb0$%8U{&Hhk|HgWvcAdFaeoS9+avy&Ot z8v`5eoBup^;C#GvZ872pn=&-niw?W}R&hMX(D^pw*ocJ})HtcFkXdp(Gn2iFj^ta( z$>%0J@Zgj}`#h|3c~)+O#&v8yD5FnvW{{d@CFi;{Fs)}p3DIPGEKE=DU`%h~m4}{< z*DtYz^Cw*gV~C5-7=DXv%5Y+Ahu*Wp6G`9@}hOu5Y~mqGT4|%(c(W z4H^qfU2|<}vr$gcsqNStHT;>fMcg9O;&2)^6a3t?Mt$S8&aj=-9TP*@#5rDrbz%=)V`sBwog3mLpPB1t)`?kksjrmR5xGD4F3QEI(zOt6 z#5PBwl*WD6%>>mOnqzvdz;s7_6Bibue-q2;PLr0}&FRZU>2~|n4ESNoR7Q!ywTk7M zIGk`bk&Ko(tpg6Fo~9F&EB*R(<6Y>UGrT@rX(xLJ1*_UC@fbk}=KMzKF-@H*h1aP# zTF+{vK#a2+DUb(#CV`_Rdj~P@svtZ;#C->IT$&8|Kiu;Y(^FBB*uu%~QFUQMyyw#GQSNi;$I|Vw?lFXgy;D`(16&(VhbW^F#S8Q9kJg7B zOZVMsnS>xd?(^v{p=9oDL5g6tRgz!QVXXzHO8B_1q`#FOoQsant6uBAmA*IKzS!NH zeyBlr2o|MQTRrmgf}y$=Uc6)J#~UIXPyZ%8Y3?nHy`XPU)+x~&7k4P#oqOC`>oVU+ zeZDN6xq4yD5;bLBjiOk#u*a>93@RylVBx_u2uKU6Q_%4Zbq{H*DnJq(7L> zhV*WxCPZ4s&}ED?x;~O^E)3M7OBgoFgt6mnoKacsy7YC-d|mqe>49pi#%ixU14v{oGQoS={?m_M{-oJt;q?*yG(FQ&y$QUd(oD-BqbO zQ=#6Ox+KkaYl*ugZ9`f@u$Ki5yFcYX3MzB}$?c1f>W)-MccgAevoETT#2qixI*TkFM$~1xxyaQG9UD3*z8t)$1`+aI~o)W{x z*Hci`7z-1BU{EIo50|e=TfU5rk?*aAi;`6@%yp zZ7e=N!u`?S=GwnGxnVkQ@PnRug{yVH5gLDM|yZi2VRdwZYr z5tSb~-#MtBsj=y8&l$W7m-rB4iEoDweZJIL$N1}p%dESDxa%iRRmaGrGN8qRafc#HEgq_}I;TUXcEI@Z*J(VsHOr%s&{ zca5Fy9v;ej(Ate%!`+Z}C=DTp()LE-7<;2j;!|GzF=_JLkoIgEta&!=0Opbr?g6Co z&9{)ui{EJWGg*$R4{PKTCTw(l7Vf&Jnhmh;?(E1RL zjP)Ter$!^jwxsLB^p5Mpdy?rL{J1MZ*D>9lp}PT3wWE=N#)qgfZa}M@8gx5PWZjLS zT_HBfx8W7ZY?68{5G0>o5sYYpv=fVVtd40IC8tW-q?#l4WF#YBA@Y~^VO;RAw))~a9flkdn80L2q zlf1*ERxml9ixqCzHO*81g~@jtXNzsYok3^r6u4_}SyJMhuq^2&cJxh2k2X?2n)Eo; zk0)(QRC`UoGI(zgZFg_b3xk)|AKYHKDD)#to9;&{OYY#BvPRXH6<0rsLg1G;n0-F1 zMWW4ui$i9@A1jc(J47Ix|hZ|gbOslg06c!)V9-QMTb zPk!Fi;|)`(Lofsm84*}uEwtLLbw`Xt-s7e~H>EPJtE{%Y<6Qk!eLK$@Znqz|Va((9 z^LC<#Ja6w}=q~$UqS{NzHeuqCNc_BwHZYM#n#gJgw|Wt3zUZKBZgM6Rh*MFf8HY66 za68!Dh62<)?h5;Mo7HA^UgNv#?FZ~e^+j#ng|K+Mu{bi{oT(S*FJPsgx8GsFJIJz8 zK)uzl`_agDo_BALe2nqe{W$We8FO(JXs)V0jU1!;SfnoE%&t`TKN{H?L0xx3dFl|T zif#TsWg0)taNmmTjRb4GYYlgAWPhYd{qUdHNf#F`O{$MX!+s%>H{id898k4S5eMTX zPj^wmfe2faI<}}81rMO^z8V*mKgx)>D{%nL;59fa5gp0M2O4Nfl3ycLv;D zIQcba;yASmJs84(zA@xJg(2Du-#C*DeH?O+hrSPCtX&dzmttgf*M}bryN`sogfSW) z40~gz`)qhKOX`Ccv%r`JxY=su|mk24#C zx#S*}z$>Vpb@_q<6SDw|<8cL_?w3uT{NDw6%keRHX7`_4_klg2A0o(2WfTY+WGs*nn| z!0S~IVW)A(vPtDIs%%y@bhxVabXxjh_IyvNibB9GSNvhEYC3H>mq)7Q`fzgMHwUxx zhGqMGnHkOfSb}>V)>`*-F=yMU&GCo0vbGOPRU@R<_B&2Wj^E!6Klx%w>r#FNxH5Ym zPm#kF*STu5SyJ2?>rct^J6Hlb3;#Dq)WKe?&i)KYzV=?EKxa=215i|kKMhNWr=YkZ zq;zH#26A#(zLVM~A*e6_!>dsTXX|35%kT`p0)~g2P`$bHY}5nR+g#Ax>=~;APNDe z7EP?0vHu|Lf6p=YbFp-jn5%O;3++Ch)YG4Qp1+Orsa5Dta&E;6;}PdI+!uGS-gf~O zdJnYq7o6j7TF4y-E1frozQsVcTNqITS^l)v5Q?#jj>EF)ey}qw@8kCu`m?x4+lTr) zBft%ur*-ag(aTXR&y5UN4WDx@*7ELvzkV0`TQI{sEHcgvi~Sjh;&12A?rgLfM%&Ia zJ>dMbh)aB5gCb1LoF5kT@$!SmsIE?478d$uUcbT`?@n{upW$3)ysr=T;C-kH9Fca1 z@?c?(Y30s^*e&)uEwC`SGasGY3Gy)3axkmJ&~r?kU93&cx~B~FM_NPcxh5;z3R;-q z$1>un0s4Djd`6b*VBz!YuR>E~;@IdDaK3VE=$bn>UX|ky=J`X+qZr=K#>&T9=gs`V z&i>SP;}AQIB|AqQ=RYC`22m!Va1o0+bh!gRzrm zqC0S_mI#2uB?Yk7yFV8~b`O7U7e890BWsAGF)+=atuX=`pVz^eCchFmBAV?|f8ca~ z6GnID@(A#pFT1ODS@P-rfJu>W8oD{!mU%f_;JwYA?N;0mw=C}C&o&uRXW|49sop7Z z0KSX>wA&cy0_G68K;H4gLV3s80q1JJ=N&&S{NKF86o#9PB&(KJUs(vnte@%K9~Ta; z?_K`+1Wy}aY|Fw6nUT&UMi&&e^J74@o%w~R@ueC5LJXH2VhaN;{N2xo>n!YphDPr+ z&B(|=S2zo7e<7T;icRfPF(V?4h z=<4N!3jG}&I9?0-Mpp#n!G(eWXYFDe14NPY4|s5KvA?+q@Gw&-#p z=Pn!@|LvHT<$Q?K0yoc})!LYakrG4?f+(t4ju|N6H#pR&%XB`%^uO&nmd1o#nAaN1cG^pzMXSpcBXui6*WQ5 zA237*oIn0Iq3>SFIWz-~;q>*U4i{4&24jCN&s~~3b91~snANgR3p4j+ItO5CXJqHeA_Rm(*y>R5FW|Fa6Vz8sX{-PiH+py-w%UEQ0&&_&z{zXS)mlPxaY_kiCnmOw+ z!MmAog{=Q^YD{O(Id8*cG+)$=2CR^C=b}Q+WzNTTVO;J5g%|y=iP3X7-|-((P6jfe z`!x03thSKWd7wAkqr7&=p(x<6#W}9o7$A~*`0XzIZi^kx0ZARf1%>{$ohajA)`>gLggKVv zB3%-CjeA)f@zG?oKmr1b~+3>6zvLDAJ*vYy8s230uJV!{TxNC{DO&184E`Ovc8fN{Hj)IfgLP z6^z6HwIGM44aHSKQ-8QOc6(qKXM@F_6+E*y=aQ!*=zp|!X1%pIR*LBCJni7|SM0iI zyPGjjI+^~kX&p1!PK7hV`v2L z%m@*^6qhk@aVO9dWj%vo(}|z+lpt!RKed~gL`^cDf(9`TW)~oBckD7}9)I3gbFSat z3PvUKtO0W%LR-`+~MV-MJqJMAJSj!}(hfz1Y(@ZkktmcYm)UeajpPmY-x~vmz;1)8xveNcPKXcvj<`c%#c_R8Ozq!s9~*4J{jLRy!X%Y|xnT-n#O= zVJHIz3^R*SUf6etHTwMFqsCk`d{qCk^ZO2oVM%u6U%#rTS&GzKdL1e8qP_CzL(Aun z!20eiV*0F^m*to*-1tB{TsE^}N)DE+pJVk`Wk;iJ5~9`Oj*E%J_(O&d8<ltrT`Qwp1)p(*0)*~6-f%Bx@m*9lErRTWKZ76s*k^6V)KivC#XUhQpG&YWva znN>S!`u_*_$=HumSY%m4nPsCYOrKU(O|9{kyhn>8O24U>jlgRQXTX|@xi#<V>0qs*JOl#x@JF)=sa9`--=ys%a)V;z+DoZ#<*#1p~&8 zC^KzMFQ9Hj-%({_hL3?%#`|MvXtX<8E3q#dJR63^`t~1CHhRqP5hnS-e%3-7DhEf( z0W-_ls@3rvm|GcFuC*FD0;^#Pn`@kRZe#aGhgN#s-zX7}85A}GFK(P#KBXcyz5Ft( zeDdU4tWR5BT|Iq4*^KhbD{8CDE-S|qZ9@kQD`P_rnpy1~mK2adL#G%KJs`JW0yH!X z>+QKz<#aAt>vH<@VE8EOr@bLnlZ^9l6<9hjhJ#si7MS71H2IkGM-3Q# zei=HDF2n;Kc~bm&8z911x(|$I#DV=xG#G9=XDDt#&mE5UVRB90_&UJx=;$=F{4M** z9E@dU^w5S;&MJewY|AkVs&RlJ`~HJQ*AFlZM9Z2mlS9Ms!bOWMyl6A0UvaM-@1Au| z!Hk;9+8L9;snxaT%&eF-vtZuT>Ulk>m_Dn#rU19T&%qco1&YatF&VdPE9MuJ_hQQF za|@70vikRyMt2rs?p8@yfV)!s5^WU;Wa8UrNjYV(#o4LvfImP2pf@hN3~3 z=+HS0>nl$!ubSR?nQ546y1~RBII5pDrFO;)uVqZb(Rmw=DnzcDSIp#w%Ik0;g`FDH@>pNkJbx>n zG?$%j4eZzNtej35pgL=G!rguI4go-_Cc8c!$MX<1_3<4;5lXvlMX`1PGH8zYbu|L`}T_^TEA)5bxx%4>Hr z{s2-R3i!m|r16XG)IYVGe?+o>WOzX8`IX_+Zl%U-Hk*0BAJfMlUjD`OeN#((WnMeS z>lS}MNxxI-#>*T&y_C>irK#CS9POJtIyGwbJqv;5{N>g%k5l=374*d$v4VY*?2s?) zkNBFnnRb@l%*nQ!+bw*p>^8o3b{D&wkB|SK@!py6Mf5~#Yj~%ej z^14EPSmOa@PCGocH)k$CyFS5GlaC4N)XNskoB30Ie)^Jlimq#t+Buj@%Mf72PxXHJ z4#6pt-+y~~a_VV9LqwhiapT3SxsqvA5YARAPrX@DA1m=p_!mzVes+#!icL7LY;3+c zH#KP*ZU>lC7@ok?Py0i39$;Dqw^AtU9EZCNSAe=K#~C)x08kNgZBj3JH$sd7_2-;S z&#PIUBmeBIvuxmy;iY|tlnsx?Mh_TMHpW~IHHvmwEiPh=J(ub7E`EQa?3C+095?W* zFy61vIWG*SE+N}m)vXF7>|mST)xIA0Aw{q2(ZHUP>aFh+!n zFiwaYL8l#F^gsqN-bj9t#I}v>=B>{`d6(qZNW|w0hgjZm68TREc==}|jP~=DRJ6Yn z3H!&9h`*AA9VHkO!lz>{Bzt*tT60Wp^C7o5)3UAtDPPZUq~qJgSkAMO`GPgdl_<9j z^1D}r+Txn6c;pg~OLGkJ*i>vmqHR%i%hwFgu}Y6DOnjJPNdIDbJ~Km>iBrYP#RcLb z@kWvV24}i^#D~Ra#6O9<#J%Ev@sM~-{8q%@2u!|wgAVhjUz4Yc{lvlIaB-qITU;vM zCax7Xid)1#iSLV?4_F@GR!e>+ekq<5aZzBx`423{YbLf9^TgA|v&FvRKyie4k$9xLxwuYzLEI~HTw%UJ9M5EH@icLeI7M76^4f>- zpBLX1-zU+Be3Kl*zb0`3b5i_K`Vht?>QlrfB+HkaD|V2+Sn}y2-{8gg10?h9UCeKo z^y4L8B2JQihUD2I-{!@9u9m!9yk7b{#otN)koc(de~|owxPwIf|3YFA{G0Uq#Sazk zV&G)F)+EaBB)OO5QWEuvi5EzJq2vi-ne=?sG2`=2k@9AOsXT=w!-zk}|wqy7k(*IrZ z0r5lWk4gSQ{EkFB@F_DglSDq*Vy@U;EELTE97Qkp4sQW9h$?{BQAl>4UgXWBrpwzD|$j zw3OUd%$L5q2qieG|z|#g@|ZMOBQ~MeHv9S(48c`$<1c@+k2l=_g5^ zDqb%AJjn~i#nN9d`6lr;>F<@iUVKRUr^PMOZx>%B(O-K=_{qPd|3o|{{l6vieO9cm z54U`nE?MMjttg)+_7Mk&6U52lbg@=kB;F=&5Vwo3itm!>kM|`Xk^D6YyH1KfN*{`P z`V_GViS(@{=ZYPqFP40|c$V}7Bo7jYNk3llB_iKg#r$VT<_oK6*F5R3mb_f#+o>4; zx03G?@0I>x$&ZOoNxxm(E&ZF~Ug`fK`CsA@68#g!tvm8`ajaNI!j5U;JaMUbv$&c> z`iCWNB2mB1;!D!+lKh(Zru6$H9~A#3{c*`(i9e7i$A=3&`ehRm%-YESYl2?msrGHrRW8zcNzbJW|__FkGNPb& zeH+Q`#EvA=_Z7#Amx^=5t4R36a>=(#zL&(dUVKRUCnY~4J}>?ZvglFt$ciWi6@NaQ;~@}=Tr>1RluEzToh z=VI|TagF#KiTt)m-YxmBBvQ=84@%q$?H& zNIys%C0;0&iIc@j66voISCX*h4u!84*Gj)Z@}uIDB+~y`{JZoA#KYoI@f#BH+o6rv zP94Mou~@`qwi%-_y_onx-mgHqZQ>H%SAe{g*W=(lx_*0t=UI>k^Lzq4NAuI|8u#G} zX8mUrW5UgSJKW?ekvv7=m6C6le5d3WC2y1bPstxk&f$G53pLiI|X=GpjIT%}^qkXhH-hQ*OA5Vi_bMOB5+|ef8(C=%Oc49YzpI+lw|#rkq@#Xj?6JMt45jMZG2P#=bITO-Ef`NrUd zqKZSr<2|7Lcu*GNC4Lx>pXac}_bF#19)IJBf8t&93>1m=Sc7^TgpT#VP*q=#o!Ey@ zRRiWT;==!Io!}c0k7FgKK`t+C@?-z7%(#zY*BUYb3u`3U7q^`@B*wdH?k}$s{Qm$= CXja|; literal 0 HcmV?d00001 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..996c58c --- /dev/null +++ b/modules/processing/gain_control/limiter/build/CMakeLists.txt @@ -0,0 +1,17 @@ +#[[ + @file CMakeLists.txt + + @brief + + @copyright + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause-Clear +]] +cmake_minimum_required(VERSION 3.10) + +add_library(limiter STATIC IMPORTED GLOBAL) + +set(lib_abs_path ${CMAKE_CURRENT_SOURCE_DIR}/../bin/arm/liblimiter.a) + +set_target_properties(limiter PROPERTIES IMPORTED_LOCATION ${lib_abs_path}) +set_property(GLOBAL APPEND PROPERTY GLOBAL_SPF_LIBS_LIST limiter) diff --git a/modules/processing/gain_control/limiter/inc/limiter24_api.h b/modules/processing/gain_control/limiter/inc/limiter24_api.h new file mode 100644 index 0000000..9c90c2c --- /dev/null +++ b/modules/processing/gain_control/limiter/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-Clear +[*===========================================================================*] +[*****************************************************************************] +[* 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/inc/limiter_api.h b/modules/processing/gain_control/limiter/inc/limiter_api.h new file mode 100644 index 0000000..3788c9e --- /dev/null +++ b/modules/processing/gain_control/limiter/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-Clear +============================================================================*/ +#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/inc/limiter_calibration_api.h b/modules/processing/gain_control/limiter/inc/limiter_calibration_api.h new file mode 100644 index 0000000..c8b08f8 --- /dev/null +++ b/modules/processing/gain_control/limiter/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-Clear +============================================================================*/ +#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 */ From af4ca98ba27a9a48129f001585ee1bb8a821a301 Mon Sep 17 00:00:00 2001 From: Annemarie Porter Date: Tue, 22 Apr 2025 15:58:54 -0700 Subject: [PATCH 2/3] modules: Add volume_control modules Add gain and volume_control modules to volume_control folder. Modify names of example_gain files to prevent a conflict with newly added gain module. Signed-off-by: Annemarie Porter --- CMakeLists.txt | 3 + arch/linux/configs/defconfig | 8 +- modules/CMakeLists.txt | 8 +- modules/Kconfig | 12 +- .../internal_api/imcl_module_gain_api.h | 110 + .../cmn/common/internal_api/imcl_mute_api.h | 68 + .../common/internal_api/imcl_p_eq_vol_api.h | 79 + modules/examples/gain/CMakeLists.txt | 24 +- ...module_api.h => example_gain_module_api.h} | 18 +- ...in_module.h => capi_example_gain_module.h} | 4 +- ...in_module.c => capi_example_gain_module.c} | 142 +- ...s.h => capi_example_gain_module_structs.h} | 54 +- ...ils.c => capi_example_gain_module_utils.c} | 150 +- ...module_lib.h => example_gain_module_lib.h} | 0 ...module_lib.c => example_gain_module_lib.c} | 2 +- .../volume_control/capi/gain/api/gain_api.h | 126 + .../capi/gain/build/CMakeLists.txt | 39 + .../volume_control/capi/gain/inc/capi_gain.h | 48 + .../volume_control/capi/gain/src/capi_gain.c | 297 +++ .../capi/gain/src/capi_gain_utils.c | 417 +++ .../capi/gain/src/capi_gain_utils.h | 96 + .../capi/gain/stub_src/capi_gain_stub.cpp | 28 + .../capi/soft_vol/api/soft_vol_api.h | 561 ++++ .../capi/soft_vol/build/CMakeLists.txt | 44 + .../capi/soft_vol/inc/capi_soft_vol.h | 40 + .../capi/soft_vol/src/capi_soft_vol.cpp | 670 +++++ .../soft_vol/src/capi_soft_vol_island.cpp | 87 + .../capi/soft_vol/src/capi_soft_vol_utils.cpp | 1225 +++++++++ .../capi/soft_vol/src/capi_soft_vol_utils.h | 296 +++ .../soft_vol/src/capi_soft_vol_utils_v2.cpp | 535 ++++ .../soft_vol/stub_src/capi_soft_vol_stub.cpp | 28 + .../lib/inc/SoftVolumeControls.h | 293 +++ .../volume_control/lib/inc/apply_gain.h | 42 + .../volume_control/lib/src/apply_gain.c | 244 ++ .../lib/src/softvolumecontrols.cpp | 783 ++++++ .../lib/src/softvolumecontrols_island.cpp | 2296 +++++++++++++++++ 36 files changed, 8674 insertions(+), 203 deletions(-) create mode 100644 modules/cmn/common/internal_api/imcl_module_gain_api.h create mode 100644 modules/cmn/common/internal_api/imcl_mute_api.h create mode 100644 modules/cmn/common/internal_api/imcl_p_eq_vol_api.h rename modules/examples/gain/api/{gain_module_api.h => example_gain_module_api.h} (92%) rename modules/examples/gain/capi/inc/{capi_gain_module.h => capi_example_gain_module.h} (91%) rename modules/examples/gain/capi/src/{capi_gain_module.c => capi_example_gain_module.c} (62%) rename modules/examples/gain/capi/src/{capi_gain_module_structs.h => capi_example_gain_module_structs.h} (58%) rename modules/examples/gain/capi/src/{capi_gain_module_utils.c => capi_example_gain_module_utils.c} (76%) rename modules/examples/gain/lib/inc/{gain_module_lib.h => example_gain_module_lib.h} (100%) rename modules/examples/gain/lib/src/{gain_module_lib.c => example_gain_module_lib.c} (99%) create mode 100644 modules/processing/volume_control/capi/gain/api/gain_api.h create mode 100644 modules/processing/volume_control/capi/gain/build/CMakeLists.txt create mode 100644 modules/processing/volume_control/capi/gain/inc/capi_gain.h create mode 100644 modules/processing/volume_control/capi/gain/src/capi_gain.c create mode 100644 modules/processing/volume_control/capi/gain/src/capi_gain_utils.c create mode 100644 modules/processing/volume_control/capi/gain/src/capi_gain_utils.h create mode 100644 modules/processing/volume_control/capi/gain/stub_src/capi_gain_stub.cpp create mode 100644 modules/processing/volume_control/capi/soft_vol/api/soft_vol_api.h create mode 100644 modules/processing/volume_control/capi/soft_vol/build/CMakeLists.txt create mode 100644 modules/processing/volume_control/capi/soft_vol/inc/capi_soft_vol.h create mode 100644 modules/processing/volume_control/capi/soft_vol/src/capi_soft_vol.cpp create mode 100644 modules/processing/volume_control/capi/soft_vol/src/capi_soft_vol_island.cpp create mode 100644 modules/processing/volume_control/capi/soft_vol/src/capi_soft_vol_utils.cpp create mode 100644 modules/processing/volume_control/capi/soft_vol/src/capi_soft_vol_utils.h create mode 100644 modules/processing/volume_control/capi/soft_vol/src/capi_soft_vol_utils_v2.cpp create mode 100644 modules/processing/volume_control/capi/soft_vol/stub_src/capi_soft_vol_stub.cpp create mode 100644 modules/processing/volume_control/lib/inc/SoftVolumeControls.h create mode 100644 modules/processing/volume_control/lib/inc/apply_gain.h create mode 100644 modules/processing/volume_control/lib/src/apply_gain.c create mode 100644 modules/processing/volume_control/lib/src/softvolumecontrols.cpp create mode 100644 modules/processing/volume_control/lib/src/softvolumecontrols_island.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index fc02e48..ccae2d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,6 +84,9 @@ 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 +modules/processing/volume_control/capi/gain/api +modules/processing/volume_control/capi/soft_vol/api +modules/cmn/common/internal_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 61aaab8..bae8eca 100644 --- a/arch/linux/configs/defconfig +++ b/arch/linux/configs/defconfig @@ -9,9 +9,9 @@ CONFIG_ARCH_LINUX=y # CONFIG_MODULES=y # CONFIG_MODULES_DEBUG is not set -CONFIG_ENCODER=y -CONFIG_ECHO_CANCELLATION=y -CONFIG_GAIN=m +CONFIG_ENCODER=n +CONFIG_ECHO_CANCELLATION=n +CONFIG_EXAMPLE_GAIN=n CONFIG_MSIIR=y CONFIG_CHMIXER=y CONFIG_PCM_CNV=y @@ -20,6 +20,8 @@ CONFIG_PCM_DECODER=y CONFIG_PCM_ENCODER=y CONFIG_DRC=y CONFIG_IIR_MBDRC=y +CONFIG_GAIN=y +CONFIG_SOFT_VOL=y # # Signal Processing Framework diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index bafaad2..0b92e58 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -22,7 +22,7 @@ endif() if(CONFIG_ECHO_CANCELLATION) add_subdirectory(examples/echo_cancellation) endif() -if(CONFIG_GAIN) +if(CONFIG_EXAMPLE_GAIN) add_subdirectory(examples/gain) endif() if(CONFIG_CHMIXER) @@ -41,3 +41,9 @@ if(CONFIG_IIR_MBDRC) add_subdirectory(processing/gain_control/limiter/build) add_subdirectory(processing/gain_control/iir_mbdrc/build) endif() +if(CONFIG_GAIN) + add_subdirectory(processing/volume_control/capi/gain/build) +endif() +if(CONFIG_SOFT_VOL) + add_subdirectory(processing/volume_control/capi/soft_vol/build) +endif() diff --git a/modules/Kconfig b/modules/Kconfig index 757487b..7c5fbb5 100644 --- a/modules/Kconfig +++ b/modules/Kconfig @@ -18,8 +18,8 @@ config ECHO_CANCELLATION tristate "Enable ECHO Cancellation Library" default y -config GAIN - tristate "Enable GAIN Library" +config EXAMPLE_GAIN + tristate "Enable EXAMPLE_GAIN Library" default y config CH_MIXER @@ -44,4 +44,12 @@ config IIR_MBDRC tristate "Enable IIR_MBDRC Library" select DRC default y + +config GAIN + tristate "Enable GAIN Library" + default y + +config SOFT_VOL + tristate "Enable SOFT_VOL Library" + default y endmenu diff --git a/modules/cmn/common/internal_api/imcl_module_gain_api.h b/modules/cmn/common/internal_api/imcl_module_gain_api.h new file mode 100644 index 0000000..46c1a39 --- /dev/null +++ b/modules/cmn/common/internal_api/imcl_module_gain_api.h @@ -0,0 +1,110 @@ +#ifndef IMCL_MODULE_GAIN_API_H +#define IMCL_MODULE_GAIN_API_H + +/** + @file imcl_module_gain_api.h + + @brief defines the Intent ID for communicating module's gain + over Inter-Module Control Links (IMCL). + +*/ + +/*========================================================================== + * Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + * SPDX-License-Identifier: BSD-3-Clause + * =========================================================================*/ + +#define INTENT_ID_GAIN_INFO 0x080010F5 +#ifdef INTENT_ID_GAIN_INFO + +#ifdef __cplusplus +extern "C" { +#endif /*__cplusplus*/ + + +/*============================================================================== + Intent ID - INTENT_ID_GAIN_INFO +==============================================================================*/ +/**< +Intent ID for the control link to send gain information. +This intent can be used by a module to send its gain to another module. + +e.g. +Soft Volume Control sends the target gain to AVC-TX module in voice path. +*/ + +/* ============================================================================ + Param ID +==============================================================================*/ +/**< + This parameter is used to send the target gain for each channel. + It may be different than current gain. + + */ +#define PARAM_ID_IMCL_TARGET_GAIN 0x080010F6 + +/*============================================================================== + Param structure defintions +==============================================================================*/ +typedef struct imcl_gain_config_t imcl_gain_config_t; + +#include "spf_begin_pack.h" +struct imcl_gain_config_t +{ + uint32_t channel_mask_lsb; + /**< @h2xmle_description {Lower 32 bits of the mask that indicates the corresponding + channel whose maximum tap length is to be set.\n + - Set the bits corresponding to 1 to 31 channels of standard + channel mapping (channels are mapped per standard channel mapping)\n + - Position of the bit to set 1 (left shift)(channel_map) \n} + @h2xmle_default {0} + */ + + uint32_t channel_mask_msb; + /**< @h2xmle_description {Upper 32 bits of the mask that indicates the corresponding channel + whose maximum tap length is to be set. \n + - Set the bits corresponding to 32 to 63 channels of standard channel + mapping (channels are mapped per standard channel mapping) \n + - Position of the bit to set 1 (left shift)(channel_map - 32)} + @h2xmle_default {0} */ + + uint32_t gain; + /**< @h2xmle_description {Gain value for the above channels in Q28 format} + @h2xmle_dataFormat {Q28} + @h2xmle_default {0} */ +} +#include "spf_end_pack.h" +; + +/* Structure definition for Parameter */ +typedef struct param_id_imcl_gain_t param_id_imcl_gain_t; + +/** @h2xmlp_parameter {"PARAM_ID_IMCL_TARGET_GAIN", + PARAM_ID_IMCL_TARGET_GAIN} + @h2xmlp_description {This parameter is used to send the target gain for each channel.} + @h2xmlp_toolPolicy {NO_SUPPORT} */ + +#include "spf_begin_pack.h" +#include "spf_begin_pragma.h" +struct param_id_imcl_gain_t +{ + uint32_t num_config; + /**< @h2xmle_description {Number of channels-gain configurations provided} + @h2xmle_range {1..63} + @h2xmle_default {1} */ + + imcl_gain_config_t gain_data[0]; + /**< @h2xmle_description {Payload consisting of all channels-gain pairs } + @h2xmle_variableArraySize {num_config} */ +} +#include "spf_end_pragma.h" +#include "spf_end_pack.h" +; + +#ifdef __cplusplus +} +#endif /*__cplusplus*/ + +#endif // INTENT_ID_GAIN_INFO + +#endif /* IMCL_MODULE_GAIN_API_H */ diff --git a/modules/cmn/common/internal_api/imcl_mute_api.h b/modules/cmn/common/internal_api/imcl_mute_api.h new file mode 100644 index 0000000..9fabd3f --- /dev/null +++ b/modules/cmn/common/internal_api/imcl_mute_api.h @@ -0,0 +1,68 @@ +#ifndef IMCL_MUTE_API_H +#define IMCL_MUTE_API_H + +/** + @file imcl_mute_api.h + + @brief defines the Intent IDs for communication over Inter-Module Control + Links (IMCL) between TTY modules(1x TTY/CTM/LTE_TTY) and Soft volume Modules + +*/ + +/*========================================================================== + * Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + * SPDX-License-Identifier: BSD-3-Clause + * =========================================================================*/ + +#define INTENT_ID_MUTE 0x08001195 +#ifdef INTENT_ID_MUTE + +#ifdef __cplusplus +extern "C" { +#endif /*__cplusplus*/ + + +/*============================================================================== + Intent ID - INTENT_ID_MUTE +==============================================================================*/ +/**< +Intent ID for the control link between TTY module(1x/CTM/LTE_TTY in Rx path) and soft volume module(in Tx path). +TTY module communicates with the soft volume module to indicate whether to +mute or unmute the stream in the Tx path. The parameter - PARAM_ID_IMCL_MUTE +is used for this communication. + +*/ + +/* ============================================================================ + Param ID +==============================================================================*/ + +#define PARAM_ID_IMCL_MUTE 0x08001196 +/** @h2xmlp_parameter {"PARAM_ID_IMCL_MUTE", PARAM_ID_IMCL_MUTE} + @h2xmlp_description {Configures the mute flag} + @h2xmlp_toolPolicy {NO_SUPPORT}*/ + +#include "spf_begin_pack.h" + +/* Payload of the PARAM_ID_IMCL_MUTE parameter used + by the Volume Control module */ + /* Structure for the mute configuration parameter for a + volume control module. */ +struct param_id_imcl_mute_t +{ + uint32_t mute_flag; +/**< @h2xmle_description {Specifies whether mute is enabled} + @h2xmle_rangeList {"Disable"= 0; + "Enable"=1} + @h2xmle_default {0} */ +} +#include "spf_end_pack.h" +; + +#ifdef __cplusplus +} +#endif /*__cplusplus*/ + +#endif + +#endif /* IMCL_MUTE_API_H */ diff --git a/modules/cmn/common/internal_api/imcl_p_eq_vol_api.h b/modules/cmn/common/internal_api/imcl_p_eq_vol_api.h new file mode 100644 index 0000000..9e2247d --- /dev/null +++ b/modules/cmn/common/internal_api/imcl_p_eq_vol_api.h @@ -0,0 +1,79 @@ +#ifndef IMCL_P_EQ_VOL_API_H +#define IMCL_P_EQ_VOL_API_H +/** + @file imcl_p_eq_vol_api.h + + @brief defines the Intent IDs for communication over Inter-Module Control + Links (IMCL) betweeen Soft Volume Module and Popless Equalizer module +*/ + +/*========================================================================== + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause + * =========================================================================*/ + +#define INTENT_ID_P_EQ_VOL_HEADROOM 0x08001118 +#ifdef INTENT_ID_P_EQ_VOL_HEADROOM + +#ifdef __cplusplus +extern "C" { +#endif /*__cplusplus*/ + + +/**< Header - Any IMCL message between soft volume module and Popless equalizer + * will have the following header followed by the actual payload. + * The peers have to parse the header accordingly*/ + +typedef struct vol_imcl_header_t +{ + // specific purpose understandable to the IMCL peers only + uint32_t opcode; + + // Size (in bytes) for the payload specific to the intent. + uint32_t actual_data_len; + +} vol_imcl_header_t; + + +#define MIN_INCOMING_IMCL_PARAM_SIZE_P_EQ_VOL (sizeof(vol_imcl_header_t) + sizeof(intf_extn_param_id_imcl_incoming_data_t)) + +/*============================================================================== + Intent ID - INTENT_ID_P_EQ_VOL_HEADROOM +==============================================================================*/ +// Intent ID for the control link between Soft Vol and Popless Equalizer. + + +/* ============================================================================ + Param ID +==============================================================================*/ + +#define PARAM_ID_P_EQ_VOL_HEADROOM 0x08001117 + +/*============================================================================== + Param structure defintions +==============================================================================*/ +/* Type definition for the above structure. */ +typedef struct p_eq_vol_headroom_param_t p_eq_vol_headroom_param_t; +/** @h2xmlp_parameter {"PARAM_ID_P_EQ_VOL_HEADROOM", PARAM_ID_P_EQ_VOL_HEADROOM} + @h2xmlp_description { Headroom in millibels communicated from popless equalizer to soft vol module\n} + @h2xmlp_toolPolicy {NO_SUPPORT} */ + +#include "spf_begin_pack.h" +struct p_eq_vol_headroom_param_t +{ + uint32_t headroom_in_millibels; + /**< @h2xmle_description {Headroom requirement of the module.} + @h2xmle_default {0x0} */ + +} +#include "spf_end_pack.h" +; + + +#ifdef __cplusplus +} +#endif /*__cplusplus*/ + +#endif // INTENT_ID_P_EQ_VOL_HEADROOM + +#endif /* #ifndef IMCL_P_EQ_VOL_API_H*/ diff --git a/modules/examples/gain/CMakeLists.txt b/modules/examples/gain/CMakeLists.txt index 71c9657..1813855 100644 --- a/modules/examples/gain/CMakeLists.txt +++ b/modules/examples/gain/CMakeLists.txt @@ -1,13 +1,13 @@ # Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. # SPDX-License-Identifier: BSD-3-Clause-Clear -set(gain_sources - capi/src/capi_gain_module.c - capi/src/capi_gain_module_utils.c - lib/src/gain_module_lib.c +set(example_gain_sources + capi/src/capi_example_gain_module.c + capi/src/capi_example_gain_module_utils.c + lib/src/example_gain_module_lib.c ) -set(gain_includes +set(example_gain_includes capi/inc lib/inc api @@ -16,18 +16,18 @@ set(gain_includes ) spf_module_sources( - KCONFIG CONFIG_GAIN - NAME gain + KCONFIG CONFIG_EXAMPLE_GAIN + NAME example_gain MAJOR_VER 1 MINOR_VER 0 AMDB_ITYPE "capi" AMDB_MTYPE "generic" AMDB_MID "0x0700106F" - AMDB_TAG "capi_gain_module" - AMDB_MOD_NAME "MODULE_ID_GAIN_MODULE" + AMDB_TAG "capi_example_gain_module" + AMDB_MOD_NAME "MODULE_ID_EXAMPLE_GAIN_MODULE" AMDB_FMT_ID1 "MEDIA_FMT_ID_EXAMPLE" - SRCS ${gain_sources} - INCLUDES ${gain_includes} - H2XML_HEADERS api/gain_module_api.h + SRCS ${example_gain_sources} + INCLUDES ${example_gain_includes} + H2XML_HEADERS api/example_gain_module_api.h CFLAGS "" ) diff --git a/modules/examples/gain/api/gain_module_api.h b/modules/examples/gain/api/example_gain_module_api.h similarity index 92% rename from modules/examples/gain/api/gain_module_api.h rename to modules/examples/gain/api/example_gain_module_api.h index 18d9de6..b127a75 100644 --- a/modules/examples/gain/api/gain_module_api.h +++ b/modules/examples/gain/api/example_gain_module_api.h @@ -3,12 +3,12 @@ /** * \file gain_module_api.h - * + * * \brief - * + * * Example Gain Module - * - * + * + * * \copyright * Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. * SPDX-License-Identifier: BSD-3-Clause-Clear @@ -38,9 +38,9 @@ ==============================================================================*/ /** Module ID for Gain module */ -#define MODULE_ID_GAIN_MODULE 0x0700106F -/** @h2xmlm_module {"MODULE_ID_GAIN_MODULE", - MODULE_ID_GAIN_MODULE} +#define MODULE_ID_EXAMPLE_GAIN_MODULE 0x0700106F +/** @h2xmlm_module {"MODULE_ID_EXAMPLE_GAIN_MODULE", + MODULE_ID_EXAMPLE_GAIN_MODULE} @h2xmlm_displayName {"Example - Gain"} @h2xmlm_description {Gain Module \n - Supports following params: @@ -71,8 +71,8 @@ /* ID of the parameter used to set the gain */ #define PARAM_ID_GAIN_MODULE_GAIN 0x08001175 -/** @h2xmlp_parameter {"PARAM_ID_GAIN_MODULE_GAIN", - PARAM_ID_GAIN_MODULE_GAIN} +/** @h2xmlp_parameter {"PARAM_ID_GAIN_MODULE", + PARAM_ID_GAIN_MODULE} @h2xmlp_description {Configures the gain} @h2xmlp_toolPolicy {Calibration; RTC} */ diff --git a/modules/examples/gain/capi/inc/capi_gain_module.h b/modules/examples/gain/capi/inc/capi_example_gain_module.h similarity index 91% rename from modules/examples/gain/capi/inc/capi_gain_module.h rename to modules/examples/gain/capi/inc/capi_example_gain_module.h index 4d2afef..01c32d7 100644 --- a/modules/examples/gain/capi/inc/capi_gain_module.h +++ b/modules/examples/gain/capi/inc/capi_example_gain_module.h @@ -32,7 +32,7 @@ extern "C"{ * Get static properties of Gain module such as * memory, stack requirements etc. */ -capi_err_t capi_gain_module_get_static_properties( +capi_err_t capi_example_gain_module_get_static_properties( capi_proplist_t *init_set_properties, capi_proplist_t *static_properties); @@ -40,7 +40,7 @@ capi_err_t capi_gain_module_get_static_properties( /** * Instantiates(and allocates) the module memory. */ -capi_err_t capi_gain_module_init( +capi_err_t capi_example_gain_module_init( capi_t *_pif, capi_proplist_t *init_set_properties); diff --git a/modules/examples/gain/capi/src/capi_gain_module.c b/modules/examples/gain/capi/src/capi_example_gain_module.c similarity index 62% rename from modules/examples/gain/capi/src/capi_gain_module.c rename to modules/examples/gain/capi/src/capi_example_gain_module.c index 67e93d7..aa00075 100644 --- a/modules/examples/gain/capi/src/capi_gain_module.c +++ b/modules/examples/gain/capi/src/capi_example_gain_module.c @@ -1,5 +1,5 @@ /** - * \file capi_gain_module.c + * \file capi_example_gain_module.c * * \brief * @@ -14,92 +14,92 @@ /*------------------------------------------------------------------------ * Include files * -----------------------------------------------------------------------*/ -#include "capi_gain_module_structs.h" +#include "capi_example_gain_module_structs.h" /*------------------------------------------------------------------------ * Static function declarations * -----------------------------------------------------------------------*/ -static capi_err_t capi_gain_module_process(capi_t *_pif, capi_stream_data_t *input[], capi_stream_data_t *output[]); +static capi_err_t capi_example_gain_module_process(capi_t *_pif, capi_stream_data_t *input[], capi_stream_data_t *output[]); -static capi_err_t capi_gain_module_end(capi_t *_pif); +static capi_err_t capi_example_gain_module_end(capi_t *_pif); -static capi_err_t capi_gain_module_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_example_gain_module_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_gain_module_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_example_gain_module_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_gain_module_set_properties(capi_t *_pif, capi_proplist_t *props_ptr); +static capi_err_t capi_example_gain_module_set_properties(capi_t *_pif, capi_proplist_t *props_ptr); -static capi_err_t capi_gain_module_get_properties(capi_t *_pif, capi_proplist_t *props_ptr); +static capi_err_t capi_example_gain_module_get_properties(capi_t *_pif, capi_proplist_t *props_ptr); /* Function table for gain module*/ -static capi_vtbl_t vtbl = { capi_gain_module_process, capi_gain_module_end, - capi_gain_module_set_param, capi_gain_module_get_param, - capi_gain_module_set_properties, capi_gain_module_get_properties }; +static capi_vtbl_t vtbl = { capi_example_gain_module_process, capi_example_gain_module_end, + capi_example_gain_module_set_param, capi_example_gain_module_get_param, + capi_example_gain_module_set_properties, capi_example_gain_module_get_properties }; /* ------------------------------------------------------------------------- - * Function name: capi_gain_module_get_static_properties + * Function name: capi_example_gain_module_get_static_properties * Used to query the static properties of the gain module that are independent of the instance. This function is used to query the memory requirements of the module in order to create an instance. * -------------------------------------------------------------------------*/ -capi_err_t capi_gain_module_get_static_properties(capi_proplist_t *init_set_properties, - capi_proplist_t *static_properties) +capi_err_t capi_example_gain_module_get_static_properties(capi_proplist_t *init_set_properties, + capi_proplist_t *static_properties) { capi_err_t capi_result = CAPI_EOK; if (NULL != static_properties) { - capi_result = capi_gain_module_process_get_properties((capi_gain_module_t *)NULL, static_properties); + capi_result = capi_example_gain_module_process_get_properties((capi_example_gain_module_t *)NULL, static_properties); if (CAPI_FAILED(capi_result)) { - AR_MSG(DBG_ERROR_PRIO, "capi_gain_module: get static properties failed!"); + AR_MSG(DBG_ERROR_PRIO, "capi_example_gain_module: get static properties failed!"); return capi_result; } else { - AR_MSG(DBG_HIGH_PRIO, "capi_gain_module: get static properties successful"); + AR_MSG(DBG_HIGH_PRIO, "capi_example_gain_module: get static properties successful"); } } else { - AR_MSG(DBG_ERROR_PRIO, "capi_gain_module: Get static properties received NULL bad pointer"); + AR_MSG(DBG_ERROR_PRIO, "capi_example_gain_module: Get static properties received NULL bad pointer"); } return capi_result; } /*------------------------------------------------------------------------ - Function name: capi_gain_module_init + Function name: capi_example_gain_module_init Instantiates the gain module to set up the virtual function table, and also allocates any memory required by the module. Default states within the module are also initialized here * -----------------------------------------------------------------------*/ -capi_err_t capi_gain_module_init(capi_t *_pif, capi_proplist_t *init_set_properties) +capi_err_t capi_example_gain_module_init(capi_t *_pif, capi_proplist_t *init_set_properties) { capi_err_t capi_result = CAPI_EOK; if (NULL == _pif || NULL == init_set_properties) { - AR_MSG(DBG_ERROR_PRIO, "capi_gain_module: Init received NULL pointers, failing"); + AR_MSG(DBG_ERROR_PRIO, "capi_example_gain_module: Init received NULL pointers, failing"); return CAPI_EBADPARAM; } /* Cast to gain module capi struct*/ - capi_gain_module_t *me_ptr = (capi_gain_module_t *)_pif; + capi_example_gain_module_t *me_ptr = (capi_example_gain_module_t *)_pif; - memset(me_ptr, 0, sizeof(capi_gain_module_t)); + memset(me_ptr, 0, sizeof(capi_example_gain_module_t)); /* Assign function table defined above*/ me_ptr->vtbl.vtbl_ptr = &vtbl; /* Initialize the operating media format of the gain module*/ - capi_gain_module_module_init_media_fmt_v2(&me_ptr->operating_media_fmt); + capi_example_gain_module_module_init_media_fmt_v2(&me_ptr->operating_media_fmt); /* Initialize gain configuration to defaults*/ uint16_t apply_gain = 0x2000; @@ -108,31 +108,31 @@ capi_err_t capi_gain_module_init(capi_t *_pif, capi_proplist_t *init_set_propert if (NULL != init_set_properties) { - capi_result = capi_gain_module_process_set_properties(me_ptr, init_set_properties); + capi_result = capi_example_gain_module_process_set_properties(me_ptr, init_set_properties); if (CAPI_FAILED(capi_result)) { - AR_MSG(DBG_ERROR_PRIO, "capi_gain_module: Initialization Set Property Failed"); + AR_MSG(DBG_ERROR_PRIO, "capi_example_gain_module: Initialization Set Property Failed"); return capi_result; } } - AR_MSG(DBG_HIGH_PRIO, "capi_gain_module: Initialization completed !!"); + AR_MSG(DBG_HIGH_PRIO, "capi_example_gain_module: Initialization completed !!"); return capi_result; } /* ------------------------------------------------------------------------- - * Function name: capi_gain_module_process + * Function name: capi_example_gain_module_process * Gain module Data Process function to process an input buffer * and fill an output buffer. * -------------------------------------------------------------------------*/ -static capi_err_t capi_gain_module_process(capi_t *_pif, capi_stream_data_t *input[], capi_stream_data_t *output[]) +static capi_err_t capi_example_gain_module_process(capi_t *_pif, capi_stream_data_t *input[], capi_stream_data_t *output[]) { capi_err_t capi_result = CAPI_EOK; - capi_gain_module_t *me_ptr = (capi_gain_module_t *)_pif; + capi_example_gain_module_t *me_ptr = (capi_example_gain_module_t *)_pif; if ((!input[0]) && (!output[0])) { - AR_MSG(DBG_ERROR_PRIO, "capi_gain_module: Process: No input or output buffer provided"); + AR_MSG(DBG_ERROR_PRIO, "capi_example_gain_module: Process: No input or output buffer provided"); return CAPI_EFAILED; } @@ -184,38 +184,38 @@ static capi_err_t capi_gain_module_process(capi_t *_pif, capi_stream_data_t *inp } /*------------------------------------------------------------------------ - * Function name: capi_gain_module_end + * Function name: capi_example_gain_module_end * Gain End function, returns the library to the uninitialized * state and frees all the memory that was allocated. This function also * frees the virtual function table. * -----------------------------------------------------------------------*/ -static capi_err_t capi_gain_module_end(capi_t *_pif) +static capi_err_t capi_example_gain_module_end(capi_t *_pif) { capi_err_t capi_result = CAPI_EOK; if (NULL == _pif) { - AR_MSG(DBG_ERROR_PRIO, "capi_gain_module: End received bad pointer, 0x%p", _pif); + AR_MSG(DBG_ERROR_PRIO, "capi_example_gain_module: End received bad pointer, 0x%p", _pif); CAPI_SET_ERROR(capi_result, CAPI_EBADPARAM); return capi_result; } - capi_gain_module_t *me_ptr = (capi_gain_module_t *)_pif; + capi_example_gain_module_t *me_ptr = (capi_example_gain_module_t *)_pif; me_ptr->vtbl.vtbl_ptr = NULL; - AR_MSG(DBG_HIGH_PRIO, "capi_gain_module: End done"); + AR_MSG(DBG_HIGH_PRIO, "capi_example_gain_module: End done"); return capi_result; } /* ------------------------------------------------------------------------- - * Function name: capi_gain_module_set_param + * Function name: capi_example_gain_module_set_param * Sets either a parameter value or a parameter structure containing * multiple parameters. In the event of a failure, the appropriate error * code is returned. * The actual_data_len field of the parameter pointer is to be at least the size of the parameter structure. Every case statement should have this check * -------------------------------------------------------------------------*/ -static capi_err_t capi_gain_module_set_param(capi_t * _pif, +static capi_err_t capi_example_gain_module_set_param(capi_t * _pif, uint32_t param_id, const capi_port_info_t *port_info_ptr, capi_buf_t * params_ptr) @@ -223,11 +223,11 @@ static capi_err_t capi_gain_module_set_param(capi_t * _pif, capi_err_t capi_result = CAPI_EOK; if (NULL == _pif || NULL == params_ptr) { - AR_MSG(DBG_ERROR_PRIO, "capi_gain_module: set param received NULL pointers"); + AR_MSG(DBG_ERROR_PRIO, "capi_example_gain_module: set param received NULL pointers"); return CAPI_EBADPARAM; } - capi_gain_module_t *me_ptr = (capi_gain_module_t *)(_pif); + capi_example_gain_module_t *me_ptr = (capi_example_gain_module_t *)(_pif); switch (param_id) { @@ -240,14 +240,14 @@ static capi_err_t capi_gain_module_set_param(capi_t * _pif, me_ptr->gain_config.enable = gain_module_enable_ptr->enable; /* Raise process check event based on enable value set here*/ - capi_result |= capi_gain_module_raise_process_event(me_ptr, me_ptr->gain_config.enable); + capi_result |= capi_example_gain_module_raise_process_event(me_ptr, me_ptr->gain_config.enable); - AR_MSG(DBG_HIGH_PRIO, "capi_gain_module: PARAM_ID_MODULE_ENABLE, %lu", me_ptr->gain_config.enable); + AR_MSG(DBG_HIGH_PRIO, "capi_example_gain_module: PARAM_ID_MODULE_ENABLE, %lu", me_ptr->gain_config.enable); } else { AR_MSG(DBG_ERROR_PRIO, - "capi_gain_module: PARAM_ID_MODULE_ENABLE, Bad param size %lu", + "capi_example_gain_module: PARAM_ID_MODULE_ENABLE, Bad param size %lu", params_ptr->actual_data_len); capi_result |= CAPI_ENEEDMORE; } @@ -273,20 +273,20 @@ static capi_err_t capi_gain_module_set_param(capi_t * _pif, enable = enable && ((me_ptr->gain_config.enable == 0) ? 0 : 1); /* Raise process check event*/ - capi_result |= capi_gain_module_raise_process_event(me_ptr, enable); + capi_result |= capi_example_gain_module_raise_process_event(me_ptr, enable); - AR_MSG(DBG_HIGH_PRIO, "capi_gain_module: PARAM_ID_GAIN %d", me_ptr->gain_config.gain_q13); + AR_MSG(DBG_HIGH_PRIO, "capi_example_gain_module: PARAM_ID_GAIN %d", me_ptr->gain_config.gain_q13); } else { - AR_MSG(DBG_ERROR_PRIO, "capi_gain_module: Set, Bad param size %lu", params_ptr->actual_data_len); + AR_MSG(DBG_ERROR_PRIO, "capi_example_gain_module: Set, Bad param size %lu", params_ptr->actual_data_len); CAPI_SET_ERROR(capi_result, CAPI_ENEEDMORE); } break; } default: { - AR_MSG(DBG_ERROR_PRIO, "capi_gain_module: Set, unsupported param ID 0x%x", (int)param_id); + AR_MSG(DBG_ERROR_PRIO, "capi_example_gain_module: Set, unsupported param ID 0x%x", (int)param_id); CAPI_SET_ERROR(capi_result, CAPI_EUNSUPPORTED); break; } @@ -295,7 +295,7 @@ static capi_err_t capi_gain_module_set_param(capi_t * _pif, } /* ------------------------------------------------------------------------- - * Function name: capi_gain_module_get_param + * Function name: capi_example_gain_module_get_param * Gets either a parameter value or a parameter structure containing * multiple parameters. In the event of a failure, the appropriate error * code is returned. @@ -304,19 +304,19 @@ static capi_err_t capi_gain_module_set_param(capi_t * _pif, * Before returning, the actual_data_len field must be filled with the number of bytes written into the buffer. * -------------------------------------------------------------------------*/ -static capi_err_t capi_gain_module_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_example_gain_module_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 capi_result = CAPI_EOK; if (NULL == _pif || NULL == params_ptr) { - AR_MSG(DBG_ERROR_PRIO, "capi_gain_module: Get param received bad pointer, 0x%p, 0x%p", _pif, params_ptr); + AR_MSG(DBG_ERROR_PRIO, "capi_example_gain_module: Get param received bad pointer, 0x%p, 0x%p", _pif, params_ptr); return CAPI_EBADPARAM; } - capi_gain_module_t *me_ptr = (capi_gain_module_t *)_pif; + capi_example_gain_module_t *me_ptr = (capi_example_gain_module_t *)_pif; switch (param_id) { @@ -332,7 +332,7 @@ static capi_err_t capi_gain_module_get_param(capi_t * _pif, } else { - AR_MSG(DBG_ERROR_PRIO, "capi_gain_module: Get Enable Param, Bad payload size %d", params_ptr->max_data_len); + AR_MSG(DBG_ERROR_PRIO, "capi_example_gain_module: Get Enable Param, Bad payload size %d", params_ptr->max_data_len); capi_result = CAPI_ENEEDMORE; } break; @@ -352,14 +352,14 @@ static capi_err_t capi_gain_module_get_param(capi_t * _pif, } else { - AR_MSG(DBG_ERROR_PRIO, "capi_gain_module: Get, Bad param size %lu", params_ptr->max_data_len); + AR_MSG(DBG_ERROR_PRIO, "capi_example_gain_module: Get, Bad param size %lu", params_ptr->max_data_len); capi_result = CAPI_ENEEDMORE; } break; } default: { - AR_MSG(DBG_ERROR_PRIO, "capi_gain_module: Get, unsupported param ID 0x%x", param_id); + AR_MSG(DBG_ERROR_PRIO, "capi_example_gain_module: Get, unsupported param ID 0x%x", param_id); capi_result |= CAPI_EUNSUPPORTED; break; } @@ -368,21 +368,21 @@ static capi_err_t capi_gain_module_get_param(capi_t * _pif, } /* ------------------------------------------------------------------------- - * Function name: capi_gain_module_set_properties + * Function name: capi_example_gain_module_set_properties * Function to set Sets a list of property values. * -------------------------------------------------------------------------*/ -static capi_err_t capi_gain_module_set_properties(capi_t *_pif, capi_proplist_t *props_ptr) +static capi_err_t capi_example_gain_module_set_properties(capi_t *_pif, capi_proplist_t *props_ptr) { - capi_gain_module_t *me_ptr = (capi_gain_module_t *)_pif; - return capi_gain_module_process_set_properties(me_ptr, props_ptr); + capi_example_gain_module_t *me_ptr = (capi_example_gain_module_t *)_pif; + return capi_example_gain_module_process_set_properties(me_ptr, props_ptr); } /* ------------------------------------------------------------------------- - * Function name: capi_gain_module_get_properties + * Function name: capi_example_gain_module_get_properties * Function to get a list of property values. * -------------------------------------------------------------------------*/ -static capi_err_t capi_gain_module_get_properties(capi_t *_pif, capi_proplist_t *props_ptr) +static capi_err_t capi_example_gain_module_get_properties(capi_t *_pif, capi_proplist_t *props_ptr) { - capi_gain_module_t *me_ptr = (capi_gain_module_t *)_pif; - return capi_gain_module_process_get_properties(me_ptr, props_ptr); + capi_example_gain_module_t *me_ptr = (capi_example_gain_module_t *)_pif; + return capi_example_gain_module_process_get_properties(me_ptr, props_ptr); } diff --git a/modules/examples/gain/capi/src/capi_gain_module_structs.h b/modules/examples/gain/capi/src/capi_example_gain_module_structs.h similarity index 58% rename from modules/examples/gain/capi/src/capi_gain_module_structs.h rename to modules/examples/gain/capi/src/capi_example_gain_module_structs.h index ac49cc8..8270bf2 100644 --- a/modules/examples/gain/capi/src/capi_gain_module_structs.h +++ b/modules/examples/gain/capi/src/capi_example_gain_module_structs.h @@ -1,7 +1,7 @@ -#ifndef CAPI_GAIN_MODULE_UTILS_H -#define CAPI_GAIN_MODULE_UTILS_H +#ifndef capi_example_gain_MODULE_UTILS_H +#define capi_example_gain_MODULE_UTILS_H /** - * \file capi_gain_module_structs.h + * \file capi_example_gain_module_structs.h * * \brief * @@ -22,9 +22,9 @@ #include "capi_util.h" #endif -#include "gain_module_api.h" -#include "capi_gain_module.h" -#include "gain_module_lib.h" +#include "example_gain_module_api.h" +#include "capi_example_gain_module.h" +#include "example_gain_module_lib.h" #include "module_cmn_api.h" #if defined(__cplusplus) @@ -35,9 +35,9 @@ extern "C" { * Macros * -----------------------------------------------------------------------*/ // KPPS number for 8KHz sampling rate and mono channel -#define CAPI_GAIN_MODULE_KPPS_8KHZ_MONO_CH 30 -#define CAPI_GAIN_MODULE_MAX_IN_PORTS 1 -#define CAPI_GAIN_MODULE_MAX_OUT_PORTS 1 +#define CAPI_EXAMPLE_GAIN_MODULE_KPPS_8KHZ_MONO_CH 30 +#define CAPI_EXAMPLE_GAIN_MODULE_MAX_IN_PORTS 1 +#define CAPI_EXAMPLE_GAIN_MODULE_MAX_OUT_PORTS 1 #define Q13_UNITY_GAIN 0x2000 static inline uint32_t gain_align_to_8_byte(const uint32_t num) @@ -49,34 +49,34 @@ static inline uint32_t gain_align_to_8_byte(const uint32_t num) * Structure definitions * -----------------------------------------------------------------------*/ /* Input/operating media format struct*/ -typedef struct capi_gain_module_media_fmt_t +typedef struct capi_example_gain_module_media_fmt_t { capi_set_get_media_format_t header; capi_standard_data_format_v2_t format; uint16_t channel_type[CAPI_MAX_CHANNELS_V2]; -} capi_gain_module_media_fmt_t; +} capi_example_gain_module_media_fmt_t; /* Struct to store current events info for the module*/ -typedef struct capi_gain_module_events_info +typedef struct capi_example_gain_module_events_info { uint32_t enable; uint32_t kpps; uint32_t delay_in_us; uint32_t code_bw; uint32_t data_bw; -} capi_gain_module_events_info_t; +} capi_example_gain_module_events_info_t; /* Struct to store gain module config*/ -typedef struct capi_gain_module_configuration +typedef struct capi_example_gain_module_configuration { uint32_t enable; /* Gain in Q13 format, set in thie format*/ uint16_t gain_q13; /* Gain stored in Q12 format t be sent to lib*/ uint16_t gain_q12; -} capi_gain_module_configuration_t; +} capi_example_gain_module_configuration_t; -typedef struct capi_gain_module_t +typedef struct capi_example_gain_module_t { /* Function table for the gain module */ capi_t vtbl; @@ -88,15 +88,15 @@ typedef struct capi_gain_module_t capi_heap_id_t heap_info; /* Input/operating media format of the gain module*/ - capi_gain_module_media_fmt_t operating_media_fmt; + capi_example_gain_module_media_fmt_t operating_media_fmt; /* Struct to store all info related to kpps, bandwidth and algorithmic delay*/ - capi_gain_module_events_info_t events_info; + capi_example_gain_module_events_info_t events_info; /* Struct to store gain configuration*/ - capi_gain_module_configuration_t gain_config; + capi_example_gain_module_configuration_t gain_config; -} capi_gain_module_t; +} capi_example_gain_module_t; #if defined(__cplusplus) } @@ -105,16 +105,16 @@ typedef struct capi_gain_module_t /*------------------------------------------------------------------------ * Function declarations * -----------------------------------------------------------------------*/ -/* For all functions in capi_gain_module_utils being called from capi_gain_module*/ +/* For all functions in capi_example_gain_module_utils being called from capi_example_gain_module*/ -capi_err_t capi_gain_module_module_init_media_fmt_v2(capi_gain_module_media_fmt_t *media_fmt_ptr); +capi_err_t capi_example_gain_module_module_init_media_fmt_v2(capi_example_gain_module_media_fmt_t *media_fmt_ptr); -capi_err_t capi_gain_module_process_set_properties(capi_gain_module_t *me_ptr, capi_proplist_t *proplist_ptr); +capi_err_t capi_example_gain_module_process_set_properties(capi_example_gain_module_t *me_ptr, capi_proplist_t *proplist_ptr); -capi_err_t capi_gain_module_process_get_properties(capi_gain_module_t *me_ptr, capi_proplist_t *proplist_ptr); +capi_err_t capi_example_gain_module_process_get_properties(capi_example_gain_module_t *me_ptr, capi_proplist_t *proplist_ptr); -capi_err_t capi_gain_module_raise_events(capi_gain_module_t *me_ptr); +capi_err_t capi_example_gain_module_raise_events(capi_example_gain_module_t *me_ptr); -capi_err_t capi_gain_module_raise_process_event(capi_gain_module_t *me_ptr, uint32_t enable); +capi_err_t capi_example_gain_module_raise_process_event(capi_example_gain_module_t *me_ptr, uint32_t enable); -#endif // CAPI_GAIN_MODULE_UTILS_H +#endif // capi_example_gain_MODULE_UTILS_H diff --git a/modules/examples/gain/capi/src/capi_gain_module_utils.c b/modules/examples/gain/capi/src/capi_example_gain_module_utils.c similarity index 76% rename from modules/examples/gain/capi/src/capi_gain_module_utils.c rename to modules/examples/gain/capi/src/capi_example_gain_module_utils.c index 259ee93..2af9a71 100644 --- a/modules/examples/gain/capi/src/capi_gain_module_utils.c +++ b/modules/examples/gain/capi/src/capi_example_gain_module_utils.c @@ -1,5 +1,5 @@ /** - * \file capi_gain_module_utils.c + * \file capi_example_gain_module_utils.c * * \brief * @@ -14,17 +14,17 @@ /*------------------------------------------------------------------------ * Include files * -----------------------------------------------------------------------*/ -#include "capi_gain_module_structs.h" +#include "capi_example_gain_module_structs.h" /*------------------------------------------------------------------------ * Functions * -----------------------------------------------------------------------*/ /* ========================================================================= - * FUNCTION : capi_gain_module_module_init_media_fmt_v2 + * FUNCTION : capi_example_gain_module_module_init_media_fmt_v2 * DESCRIPTION: Function to initialize the media format of the gain module * =========================================================================*/ -capi_err_t capi_gain_module_module_init_media_fmt_v2(capi_gain_module_media_fmt_t *media_fmt_ptr) +capi_err_t capi_example_gain_module_module_init_media_fmt_v2(capi_example_gain_module_media_fmt_t *media_fmt_ptr) { media_fmt_ptr->header.format_header.data_format = CAPI_FIXED_POINT; media_fmt_ptr->format.minor_version = CAPI_DATA_FORMAT_INVALID_VAL; @@ -44,11 +44,11 @@ capi_err_t capi_gain_module_module_init_media_fmt_v2(capi_gain_module_media_fmt_ } /* ========================================================================= - * FUNCTION : capi_gain_module_raise_process_event + * FUNCTION : capi_example_gain_module_raise_process_event * DESCRIPTION: Function to raise the process check event using the * callback function to inform the framework if module is enabled or disabled * =========================================================================*/ -capi_err_t capi_gain_module_raise_process_event(capi_gain_module_t *me_ptr, uint32_t enable) +capi_err_t capi_example_gain_module_raise_process_event(capi_example_gain_module_t *me_ptr, uint32_t enable) { capi_err_t capi_result = CAPI_EOK; @@ -57,7 +57,7 @@ capi_err_t capi_gain_module_raise_process_event(capi_gain_module_t *me_ptr, uint { if (NULL == me_ptr->cb_info.event_cb) { - AR_MSG(DBG_ERROR_PRIO, "capi_gain_module : Event callback is not set, Unable to raise process check event!"); + AR_MSG(DBG_ERROR_PRIO, "capi_example_gain_module : Event callback is not set, Unable to raise process check event!"); return CAPI_EBADPARAM; } @@ -80,23 +80,23 @@ capi_err_t capi_gain_module_raise_process_event(capi_gain_module_t *me_ptr, uint if (CAPI_FAILED(capi_result)) { AR_MSG(DBG_ERROR_PRIO, - "capi_gain_module : Failed to send process check event with result %d", + "capi_example_gain_module : Failed to send process check event with result %d", (int)capi_result); } else { - AR_MSG(DBG_HIGH_PRIO, "capi_gain_module : Raising process check event with enable set to %lu", enable); + AR_MSG(DBG_HIGH_PRIO, "capi_example_gain_module : Raising process check event with enable set to %lu", enable); } } return capi_result; } /* ========================================================================= - * FUNCTION : capi_gain_module_raise_process_event + * FUNCTION : capi_example_gain_module_raise_process_event * DESCRIPTION: Function to raise the process check event using the * callback function to inform the framework if module is enabled or disabled * =========================================================================*/ -static capi_err_t capi_gain_module_raise_kpps_event(capi_gain_module_t *me_ptr, uint32_t kpps) +static capi_err_t capi_example_gain_module_raise_kpps_event(capi_example_gain_module_t *me_ptr, uint32_t kpps) { capi_err_t result = CAPI_EOK; @@ -124,18 +124,18 @@ static capi_err_t capi_gain_module_raise_kpps_event(capi_gain_module_t *me_ptr, result = me_ptr->cb_info.event_cb(me_ptr->cb_info.event_context, CAPI_EVENT_KPPS, &event_info); if (CAPI_FAILED(result)) { - AR_MSG(DBG_ERROR_PRIO, "capi_gain_module : Failed to send KPPS raise event with %lu", result); + AR_MSG(DBG_ERROR_PRIO, "capi_example_gain_module : Failed to send KPPS raise event with %lu", result); } } return result; } /* ========================================================================= - * FUNCTION : capi_gain_module_raise_process_event + * FUNCTION : capi_example_gain_module_raise_process_event * DESCRIPTION: Function to raise the process check event using the * callback function to inform the framework if module is enabled or disabled * =========================================================================*/ -static capi_err_t capi_gain_module_raise_bandwidth_event(capi_gain_module_t *me_ptr, +static capi_err_t capi_example_gain_module_raise_bandwidth_event(capi_example_gain_module_t *me_ptr, uint32_t code_bandwidth, uint32_t data_bandwidth) { @@ -166,18 +166,18 @@ static capi_err_t capi_gain_module_raise_bandwidth_event(capi_gain_module_t *me_ result = me_ptr->cb_info.event_cb(me_ptr->cb_info.event_context, CAPI_EVENT_BANDWIDTH, &event_info); if (CAPI_FAILED(result)) { - AR_MSG(DBG_ERROR_PRIO, "capi_gain_module : Failed to send bandwidth raise event with %lu", result); + AR_MSG(DBG_ERROR_PRIO, "capi_example_gain_module : Failed to send bandwidth raise event with %lu", result); } } return result; } /* ========================================================================= - * FUNCTION : capi_gain_module_raise_process_event + * FUNCTION : capi_example_gain_module_raise_process_event * DESCRIPTION: Function to raise the process check event using the * callback function to inform the framework if module is enabled or disabled * =========================================================================*/ -static capi_err_t capi_gain_module_raise_algo_delay_event(capi_gain_module_t *me_ptr, uint32_t delay_in_us) +static capi_err_t capi_example_gain_module_raise_algo_delay_event(capi_example_gain_module_t *me_ptr, uint32_t delay_in_us) { capi_err_t result = CAPI_EOK; @@ -204,52 +204,52 @@ static capi_err_t capi_gain_module_raise_algo_delay_event(capi_gain_module_t *me if (CAPI_FAILED(result)) { - AR_MSG(DBG_ERROR_PRIO, "capi_gain_module : Failed to send delay raise event with %lu", result); + AR_MSG(DBG_ERROR_PRIO, "capi_example_gain_module : Failed to send delay raise event with %lu", result); } } return result; } /* ========================================================================= - * FUNCTION : capi_gain_module_raise_event + * FUNCTION : capi_example_gain_module_raise_event * DESCRIPTION: Function to raise kpps, bandwidth and algorithmic delay events * for the gain module * =========================================================================*/ -capi_err_t capi_gain_module_raise_events(capi_gain_module_t *me_ptr) +capi_err_t capi_example_gain_module_raise_events(capi_example_gain_module_t *me_ptr) { capi_err_t capi_result = CAPI_EOK; if (NULL == me_ptr->cb_info.event_cb) { - AR_MSG(DBG_ERROR_PRIO, "capi_gain_module: Event callback is not set. Unable to raise events!"); + AR_MSG(DBG_ERROR_PRIO, "capi_example_gain_module: Event callback is not set. Unable to raise events!"); return CAPI_EFAILED; } /* scale KPPS based on num channels and sampling rate */ - uint32_t kpps = CAPI_GAIN_MODULE_KPPS_8KHZ_MONO_CH * me_ptr->operating_media_fmt.format.num_channels * + uint32_t kpps = CAPI_EXAMPLE_GAIN_MODULE_KPPS_8KHZ_MONO_CH * me_ptr->operating_media_fmt.format.num_channels * (me_ptr->operating_media_fmt.format.sampling_rate / 8000); /* Raise kpps, bw and algo delay events by passing in necessary values*/ - capi_result |= capi_gain_module_raise_kpps_event(me_ptr, kpps); + capi_result |= capi_example_gain_module_raise_kpps_event(me_ptr, kpps); - capi_result |= capi_gain_module_raise_bandwidth_event(me_ptr, 0 /*code bw*/, 0 /*data bw*/); + capi_result |= capi_example_gain_module_raise_bandwidth_event(me_ptr, 0 /*code bw*/, 0 /*data bw*/); - capi_result |= capi_gain_module_raise_algo_delay_event(me_ptr, 0 /*delay in us*/); + capi_result |= capi_example_gain_module_raise_algo_delay_event(me_ptr, 0 /*delay in us*/); return capi_result; } /* ========================================================================= - * FUNCTION : capi_gain_module_output_media_fmt_event_v2 + * FUNCTION : capi_example_gain_module_output_media_fmt_event_v2 * DESCRIPTION: Function to raiseoutput media format event * =========================================================================*/ -capi_err_t capi_gain_module_output_media_fmt_event_v2(capi_event_callback_info_t * cb_info_ptr, - capi_gain_module_media_fmt_t *out_media_fmt) +capi_err_t capi_example_gain_module_output_media_fmt_event_v2(capi_event_callback_info_t * cb_info_ptr, + capi_example_gain_module_media_fmt_t *out_media_fmt) { capi_err_t result = CAPI_EOK; if ((NULL == cb_info_ptr->event_cb) || (NULL == out_media_fmt)) { AR_MSG(DBG_ERROR_PRIO, - "capi_gain_module : Event callback is not set or media fmt is NULL, Unable to raise output " + "capi_example_gain_module : Event callback is not set or media fmt is NULL, Unable to raise output " "media fmt v2 event!"); return CAPI_EBADPARAM; } @@ -260,7 +260,7 @@ capi_err_t capi_gain_module_output_media_fmt_event_v2(capi_event_callback_info_t event_info.port_info.is_input_port = FALSE; event_info.payload.actual_data_len = (sizeof(capi_standard_data_format_v2_t) + sizeof(capi_set_get_media_format_t) + (sizeof(out_media_fmt->channel_type[0]) * out_media_fmt->format.num_channels)); - event_info.payload.max_data_len = sizeof(capi_gain_module_media_fmt_t); + event_info.payload.max_data_len = sizeof(capi_example_gain_module_media_fmt_t); event_info.payload.data_ptr = (int8_t *)out_media_fmt; result = cb_info_ptr->event_cb(cb_info_ptr->event_context, CAPI_EVENT_OUTPUT_MEDIA_FORMAT_UPDATED_V2, &event_info); @@ -268,13 +268,13 @@ capi_err_t capi_gain_module_output_media_fmt_event_v2(capi_event_callback_info_t if (CAPI_FAILED(result)) { AR_MSG(DBG_ERROR_PRIO, - "capi_gain_module: Failed to send output media format updated event V2 with result %d", + "capi_example_gain_module: Failed to send output media format updated event V2 with result %d", (int)result); } else { AR_MSG(DBG_HIGH_PRIO, - "capi_gain_module: Raised output media fmt event with Sampling rate = %lu, num channels = %lu, bits per " + "capi_example_gain_module: Raised output media fmt event with Sampling rate = %lu, num channels = %lu, bits per " "sample =%lu", out_media_fmt->format.sampling_rate, out_media_fmt->format.num_channels, @@ -284,16 +284,16 @@ capi_err_t capi_gain_module_output_media_fmt_event_v2(capi_event_callback_info_t } /* ========================================================================= - * FUNCTION : capi_gain_module_process_set_properties + * FUNCTION : capi_example_gain_module_process_set_properties * DESCRIPTION: Implementation of set properties * =========================================================================*/ -capi_err_t capi_gain_module_process_set_properties(capi_gain_module_t *me_ptr, capi_proplist_t *proplist_ptr) +capi_err_t capi_example_gain_module_process_set_properties(capi_example_gain_module_t *me_ptr, capi_proplist_t *proplist_ptr) { capi_err_t capi_result = CAPI_EOK, capi_result2 = CAPI_EOK; if (NULL == me_ptr) { - AR_MSG(DBG_ERROR_PRIO, "capi_gain_module : Set property received null ptr"); + AR_MSG(DBG_ERROR_PRIO, "capi_example_gain_module : Set property received null ptr"); return CAPI_EBADPARAM; } @@ -317,7 +317,7 @@ capi_err_t capi_gain_module_process_set_properties(capi_gain_module_t *me_ptr, c else { AR_MSG(DBG_ERROR_PRIO, - "capi_gain_module: Set property id 0x%lx Bad param size %lu", + "capi_example_gain_module: Set property id 0x%lx Bad param size %lu", (uint32_t)prop_array[i].id, payload_ptr->actual_data_len); capi_result |= CAPI_ENEEDMORE; @@ -334,7 +334,7 @@ capi_err_t capi_gain_module_process_set_properties(capi_gain_module_t *me_ptr, c else { AR_MSG(DBG_ERROR_PRIO, - "capi_gain_module: Set property id 0x%lx Bad param size %lu", + "capi_example_gain_module: Set property id 0x%lx Bad param size %lu", (uint32_t)prop_array[i].id, payload_ptr->actual_data_len); capi_result |= CAPI_ENEEDMORE; @@ -346,21 +346,21 @@ capi_err_t capi_gain_module_process_set_properties(capi_gain_module_t *me_ptr, c if (payload_ptr->actual_data_len >= sizeof(capi_port_num_info_t)) { capi_port_num_info_t *data_ptr = (capi_port_num_info_t *)payload_ptr->data_ptr; - if ((CAPI_GAIN_MODULE_MAX_IN_PORTS < data_ptr->num_input_ports) || - (CAPI_GAIN_MODULE_MAX_OUT_PORTS < data_ptr->num_output_ports)) + if ((CAPI_EXAMPLE_GAIN_MODULE_MAX_IN_PORTS < data_ptr->num_input_ports) || + (CAPI_EXAMPLE_GAIN_MODULE_MAX_OUT_PORTS < data_ptr->num_output_ports)) { AR_MSG(DBG_ERROR_PRIO, - "capi_gain_module: Set property id 0x%lx,number of input/output ports cannot be more than " + "capi_example_gain_module: Set property id 0x%lx,number of input/output ports cannot be more than " "%d", (uint32_t)prop_array[i].id, - CAPI_GAIN_MODULE_MAX_OUT_PORTS); + CAPI_EXAMPLE_GAIN_MODULE_MAX_OUT_PORTS); capi_result |= CAPI_ENEEDMORE; } } else { AR_MSG(DBG_ERROR_PRIO, - "capi_gain_module: Set property id 0x%lx Bad param size %lu", + "capi_example_gain_module: Set property id 0x%lx Bad param size %lu", (uint32_t)prop_array[i].id, payload_ptr->actual_data_len); capi_result |= CAPI_ENEEDMORE; @@ -369,18 +369,18 @@ capi_err_t capi_gain_module_process_set_properties(capi_gain_module_t *me_ptr, c } case CAPI_INPUT_MEDIA_FORMAT_V2: { - if (payload_ptr->actual_data_len >= sizeof(capi_gain_module_media_fmt_t)) + if (payload_ptr->actual_data_len >= sizeof(capi_example_gain_module_media_fmt_t)) { - AR_MSG(DBG_HIGH_PRIO, "capi_gain_module: Received Input media format"); + AR_MSG(DBG_HIGH_PRIO, "capi_example_gain_module: Received Input media format"); - capi_gain_module_media_fmt_t *media_fmt_ptr = (capi_gain_module_media_fmt_t *)(payload_ptr->data_ptr); + capi_example_gain_module_media_fmt_t *media_fmt_ptr = (capi_example_gain_module_media_fmt_t *)(payload_ptr->data_ptr); /* Number of channels should be between 0 and 32*/ if ((0 >= media_fmt_ptr->format.num_channels) || (CAPI_MAX_CHANNELS_V2 < media_fmt_ptr->format.num_channels)) { AR_MSG(DBG_ERROR_PRIO, - "capi_gain_module: Unsupported number of channels %lu", + "capi_example_gain_module: Unsupported number of channels %lu", media_fmt_ptr->format.num_channels); return CAPI_EBADPARAM; } @@ -396,7 +396,7 @@ capi_err_t capi_gain_module_process_set_properties(capi_gain_module_t *me_ptr, c if (payload_ptr->actual_data_len < required_size) { AR_MSG(DBG_ERROR_PRIO, - "capi_gain_module: Not valid media format size %lu, required size %lu", + "capi_example_gain_module: Not valid media format size %lu, required size %lu", payload_ptr->actual_data_len, required_size); return CAPI_ENEEDMORE; @@ -406,7 +406,7 @@ capi_err_t capi_gain_module_process_set_properties(capi_gain_module_t *me_ptr, c if (CAPI_FIXED_POINT != media_fmt_ptr->header.format_header.data_format) { AR_MSG(DBG_ERROR_PRIO, - "capi_gain_module: unsupported data format %lu", + "capi_example_gain_module: unsupported data format %lu", (uint32_t)media_fmt_ptr->header.format_header.data_format); return CAPI_EBADPARAM; } @@ -415,22 +415,22 @@ capi_err_t capi_gain_module_process_set_properties(capi_gain_module_t *me_ptr, c if ((16 != media_fmt_ptr->format.bits_per_sample) && (32 != media_fmt_ptr->format.bits_per_sample)) { AR_MSG(DBG_ERROR_PRIO, - "capi_gain_module: only supports 16 and 32 bit data. Received %lu.", + "capi_example_gain_module: only supports 16 and 32 bit data. Received %lu.", media_fmt_ptr->format.bits_per_sample); return CAPI_EBADPARAM; } /* Validate interleaving*/ - if (media_fmt_ptr->format.num_channels != 1 && media_fmt_ptr->format.data_interleaving != CAPI_DEINTERLEAVED_UNPACKED) + if (media_fmt_ptr->format.data_interleaving != CAPI_DEINTERLEAVED_UNPACKED) { - AR_MSG(DBG_ERROR_PRIO, "capi_gain_module : Interleaved data not supported."); + AR_MSG(DBG_ERROR_PRIO, "capi_example_gain_module : Interleaved data not supported."); return CAPI_EBADPARAM; } /* Validate data signed/unsigned*/ if (!media_fmt_ptr->format.data_is_signed) { - AR_MSG(DBG_ERROR_PRIO, "capi_gain_module: Unsigned data not supported."); + AR_MSG(DBG_ERROR_PRIO, "capi_example_gain_module: Unsigned data not supported."); return CAPI_EBADPARAM; } @@ -438,7 +438,7 @@ capi_err_t capi_gain_module_process_set_properties(capi_gain_module_t *me_ptr, c if ((0 >= media_fmt_ptr->format.sampling_rate) || (384000 < media_fmt_ptr->format.sampling_rate)) { AR_MSG(DBG_ERROR_PRIO, - "capi_gain_module: Unsupported sampling rate %lu", + "capi_example_gain_module: Unsupported sampling rate %lu", media_fmt_ptr->format.sampling_rate); return CAPI_EBADPARAM; } @@ -447,14 +447,14 @@ capi_err_t capi_gain_module_process_set_properties(capi_gain_module_t *me_ptr, c memscpy(&me_ptr->operating_media_fmt, required_size, media_fmt_ptr, payload_ptr->actual_data_len); /* raise event for output media format */ - capi_result |= capi_gain_module_raise_events(me_ptr); + capi_result |= capi_example_gain_module_raise_events(me_ptr); capi_result |= - capi_gain_module_output_media_fmt_event_v2(&me_ptr->cb_info, &me_ptr->operating_media_fmt); + capi_example_gain_module_output_media_fmt_event_v2(&me_ptr->cb_info, &me_ptr->operating_media_fmt); } else { AR_MSG(DBG_ERROR_PRIO, - "capi_gain_module: Set property id 0x%lx Bad param size %lu", + "capi_example_gain_module: Set property id 0x%lx Bad param size %lu", (uint32_t)prop_array[i].id, payload_ptr->actual_data_len); capi_result |= CAPI_ENEEDMORE; @@ -470,7 +470,7 @@ capi_err_t capi_gain_module_process_set_properties(capi_gain_module_t *me_ptr, c } default: { - AR_MSG(DBG_HIGH_PRIO, "capi_gain_module: Set property id %x. Not supported.", prop_array[i].id); + AR_MSG(DBG_HIGH_PRIO, "capi_example_gain_module: Set property id %x. Not supported.", prop_array[i].id); break; } } @@ -485,10 +485,10 @@ capi_err_t capi_gain_module_process_set_properties(capi_gain_module_t *me_ptr, c } /* ========================================================================= - * FUNCTION : capi_gain_module_process_get_properties + * FUNCTION : capi_example_gain_module_process_get_properties * DESCRIPTION: Combined implementation of get static properties and get properties * =========================================================================*/ -capi_err_t capi_gain_module_process_get_properties(capi_gain_module_t *me_ptr, capi_proplist_t *proplist_ptr) +capi_err_t capi_example_gain_module_process_get_properties(capi_example_gain_module_t *me_ptr, capi_proplist_t *proplist_ptr) { capi_err_t capi_result = CAPI_EOK; capi_err_t capi_result2 = CAPI_EOK; @@ -499,7 +499,7 @@ capi_err_t capi_gain_module_process_get_properties(capi_gain_module_t *me_ptr, c capi_buf_t *payload_ptr = &prop_array[i].payload; if (NULL == payload_ptr->data_ptr) { - AR_MSG(DBG_ERROR_PRIO, "capi_gain_module: Get property id 0x%x, received null buffer", prop_array[i].id); + AR_MSG(DBG_ERROR_PRIO, "capi_example_gain_module: Get property id 0x%x, received null buffer", prop_array[i].id); capi_result |= CAPI_EBADPARAM; break; } @@ -511,13 +511,13 @@ capi_err_t capi_gain_module_process_get_properties(capi_gain_module_t *me_ptr, c if (payload_ptr->max_data_len >= sizeof(capi_init_memory_requirement_t)) { capi_init_memory_requirement_t *data_ptr = (capi_init_memory_requirement_t *)(payload_ptr->data_ptr); - data_ptr->size_in_bytes = gain_align_to_8_byte(sizeof(capi_gain_module_t)); + data_ptr->size_in_bytes = gain_align_to_8_byte(sizeof(capi_example_gain_module_t)); payload_ptr->actual_data_len = sizeof(capi_init_memory_requirement_t); } else { AR_MSG(DBG_ERROR_PRIO, - "capi_gain_module: Get property id 0x%lx Bad param size %lu", + "capi_example_gain_module: Get property id 0x%lx Bad param size %lu", (uint32_t)prop_array[i].id, payload_ptr->actual_data_len); capi_result |= CAPI_ENEEDMORE; @@ -536,7 +536,7 @@ capi_err_t capi_gain_module_process_get_properties(capi_gain_module_t *me_ptr, c else { AR_MSG(DBG_ERROR_PRIO, - "capi_gain_module: Get property id 0x%lx Bad param size %lu", + "capi_example_gain_module: Get property id 0x%lx Bad param size %lu", (uint32_t)prop_array[i].id, payload_ptr->actual_data_len); capi_result |= CAPI_ENEEDMORE; @@ -554,7 +554,7 @@ capi_err_t capi_gain_module_process_get_properties(capi_gain_module_t *me_ptr, c else { AR_MSG(DBG_ERROR_PRIO, - "capi_gain_module: Get property id 0x%lx Bad param size %lu", + "capi_example_gain_module: Get property id 0x%lx Bad param size %lu", (uint32_t)prop_array[i].id, payload_ptr->max_data_len); capi_result |= CAPI_ENEEDMORE; @@ -572,7 +572,7 @@ capi_err_t capi_gain_module_process_get_properties(capi_gain_module_t *me_ptr, c else { AR_MSG(DBG_ERROR_PRIO, - "capi_gain_module: Get property id 0x%lx Bad param size %lu", + "capi_example_gain_module: Get property id 0x%lx Bad param size %lu", (uint32_t)prop_array[i].id, payload_ptr->max_data_len); capi_result |= CAPI_ENEEDMORE; @@ -602,7 +602,7 @@ capi_err_t capi_gain_module_process_get_properties(capi_gain_module_t *me_ptr, c { if (NULL == me_ptr) { - AR_MSG(DBG_ERROR_PRIO, "capi_gain_module : null ptr while querying output mf"); + AR_MSG(DBG_ERROR_PRIO, "capi_example_gain_module : null ptr while querying output mf"); capi_result |= CAPI_EBADPARAM; break; } @@ -610,15 +610,15 @@ capi_err_t capi_gain_module_process_get_properties(capi_gain_module_t *me_ptr, c /* The size of the payload is the sum of the size of the media format struct and the channel map size * which depends on the number of channels. Every channel will have a uint16_t channel type field*/ uint32_t total_size = - sizeof(capi_gain_module_media_fmt_t) + (sizeof(me_ptr->operating_media_fmt.format.channel_type[0]) * + sizeof(capi_example_gain_module_media_fmt_t) + (sizeof(me_ptr->operating_media_fmt.format.channel_type[0]) * me_ptr->operating_media_fmt.format.num_channels); if (payload_ptr->max_data_len >= total_size) { - capi_gain_module_media_fmt_t *data_ptr = (capi_gain_module_media_fmt_t *)payload_ptr->data_ptr; + capi_example_gain_module_media_fmt_t *data_ptr = (capi_example_gain_module_media_fmt_t *)payload_ptr->data_ptr; if ((FALSE == prop_array[i].port_info.is_valid) && (TRUE == prop_array[i].port_info.is_input_port)) { - AR_MSG(DBG_ERROR_PRIO, "capi_gain_module : Get output media fmt v2 port id not valid or input port"); + AR_MSG(DBG_ERROR_PRIO, "capi_example_gain_module : Get output media fmt v2 port id not valid or input port"); capi_result |= CAPI_EBADPARAM; } memscpy(data_ptr, total_size, &me_ptr->operating_media_fmt, total_size); @@ -627,7 +627,7 @@ capi_err_t capi_gain_module_process_get_properties(capi_gain_module_t *me_ptr, c else { AR_MSG(DBG_ERROR_PRIO, - "capi_gain_module: Get property_id 0x%lx, Bad param size %lu", + "capi_example_gain_module: Get property_id 0x%lx, Bad param size %lu", (uint32_t)prop_array[i].id, payload_ptr->max_data_len); payload_ptr->actual_data_len = 0; @@ -639,7 +639,7 @@ capi_err_t capi_gain_module_process_get_properties(capi_gain_module_t *me_ptr, c { if (NULL == me_ptr) { - AR_MSG(DBG_ERROR_PRIO, "capi_gain_module : null ptr while querying threshold"); + AR_MSG(DBG_ERROR_PRIO, "capi_example_gain_module : null ptr while querying threshold"); return CAPI_EBADPARAM; } if (payload_ptr->max_data_len >= sizeof(capi_port_data_threshold_t)) @@ -647,13 +647,13 @@ capi_err_t capi_gain_module_process_get_properties(capi_gain_module_t *me_ptr, c capi_port_data_threshold_t *data_ptr = (capi_port_data_threshold_t *)payload_ptr; if (!prop_array[i].port_info.is_valid) { - AR_MSG(DBG_ERROR_PRIO, "capi_gain_module : Get port threshold port id not valid"); + AR_MSG(DBG_ERROR_PRIO, "capi_example_gain_module : Get port threshold port id not valid"); capi_result |= CAPI_EBADPARAM; } if (0 != prop_array[i].port_info.port_index) { AR_MSG(DBG_ERROR_PRIO, - "capi_gain_module: Get property_id 0x%lx, max in/out port is 1. asking for %lu", + "capi_example_gain_module: Get property_id 0x%lx, max in/out port is 1. asking for %lu", (uint32_t)prop_array[i].id, prop_array[i].port_info.port_index); capi_result |= CAPI_EBADPARAM; @@ -666,7 +666,7 @@ capi_err_t capi_gain_module_process_get_properties(capi_gain_module_t *me_ptr, c else { AR_MSG(DBG_ERROR_PRIO, - "capi_gain_module: Get property_id 0x%lx, Bad param size %lu", + "capi_example_gain_module: Get property_id 0x%lx, Bad param size %lu", (uint32_t)prop_array[i].id, payload_ptr->max_data_len); payload_ptr->actual_data_len = 0; @@ -683,7 +683,7 @@ capi_err_t capi_gain_module_process_get_properties(capi_gain_module_t *me_ptr, c } default: { - AR_MSG(DBG_HIGH_PRIO, "capi_gain_module: Get property for ID %#x. Not supported.", prop_array[i].id); + AR_MSG(DBG_HIGH_PRIO, "capi_example_gain_module: Get property for ID %#x. Not supported.", prop_array[i].id); capi_result |= CAPI_EUNSUPPORTED; break; } diff --git a/modules/examples/gain/lib/inc/gain_module_lib.h b/modules/examples/gain/lib/inc/example_gain_module_lib.h similarity index 100% rename from modules/examples/gain/lib/inc/gain_module_lib.h rename to modules/examples/gain/lib/inc/example_gain_module_lib.h diff --git a/modules/examples/gain/lib/src/gain_module_lib.c b/modules/examples/gain/lib/src/example_gain_module_lib.c similarity index 99% rename from modules/examples/gain/lib/src/gain_module_lib.c rename to modules/examples/gain/lib/src/example_gain_module_lib.c index ed25b31..33928d6 100644 --- a/modules/examples/gain/lib/src/gain_module_lib.c +++ b/modules/examples/gain/lib/src/example_gain_module_lib.c @@ -12,7 +12,7 @@ */ -#include "gain_module_lib.h" +#include "example_gain_module_lib.h" /*============================================================================= FUNCTION void example_apply_gain_16 diff --git a/modules/processing/volume_control/capi/gain/api/gain_api.h b/modules/processing/volume_control/capi/gain/api/gain_api.h new file mode 100644 index 0000000..4b2b130 --- /dev/null +++ b/modules/processing/volume_control/capi/gain/api/gain_api.h @@ -0,0 +1,126 @@ +#ifndef _GAIN_API_H_ +#define _GAIN_API_H_ + +/*============================================================================== + @file gain_api.h + @brief This file contains Gain module APIs + + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause-Clear +==============================================================================*/ + + /*------------------------------------------------------------------------ + * Include files + * -----------------------------------------------------------------------*/ +#include "module_cmn_api.h" + +/** @h2xml_title1 {Gain API} + @h2xml_title_agile_rev {Gain API} + @h2xml_title_date {July 15, 2018} */ + +/*------------------------------------------------------------------------------ + Defines +------------------------------------------------------------------------------*/ +/** @ingroup ar_spf_mod_gain_macros + Input port ID of the Gain module. */ +#define GAIN_DATA_INPUT_PORT 0x2 + +/** @ingroup ar_spf_mod_gain_macros + Output port ID of the Gain module. */ +#define GAIN_DATA_OUTPUT_PORT 0x1 + +/** @ingroup ar_spf_mod_gain_macros + Stack size of the Gain module. */ +#define GAIN_STACK_SIZE 4096 +/*============================================================================== + Module +==============================================================================*/ + +/** @ingroup ar_spf_mod_gain_macros + Gain module. + + @subhead4{Supported parameter IDs} + - PARAM_ID_GAIN + + @subhead4{Supported input media format ID} + - Data Format : FIXED_POINT @lstsp1 + - fmt_id : Don't care @lstsp1 + - Sample Rates : Don't care @lstsp1 + - Number of channels : 1 to 128 (for certain products this module supports only 32 channels) @lstsp1 + - Channel type : Don't care @lstsp1 + - Bits per sample : 16, 32 @lstsp1 + - Q format : Don't care @lstsp1 + - Interleaving : De-interleaved unpacked @lstsp1 + - Signed/unsigned : Signed @lstsp1 + */ +#define MODULE_ID_GAIN 0x07001002 +/** @h2xmlm_module {"MODULE_ID_GAIN", + MODULE_ID_GAIN} + @h2xmlm_displayName {"Gain"} + @h2xmlm_modSearchKeys{gain, Audio} + @h2xmlm_description {Gain Module \n + - Supports following params: + - PARAM_ID_GAIN + - \n + - Supported Input Media Format: + - Data Format : FIXED_POINT + - fmt_id : Don't care + - Sample Rates : Don't care + - Number of channels : 1 to 128 (for certain products this module supports only 32 channels) + - Channel type : Don't care + - Bits per sample : 16, 32 + - Q format : Don't care + - Interleaving : de-interleaved unpacked + - Signed/unsigned : Signed} + @h2xmlm_dataMaxInputPorts { 1 } + @h2xmlm_dataInputPorts { IN = GAIN_DATA_INPUT_PORT} + @h2xmlm_dataOutputPorts { OUT = GAIN_DATA_OUTPUT_PORT} + @h2xmlm_dataMaxOutputPorts { 1 } + @h2xmlm_supportedContTypes {APM_CONTAINER_TYPE_SC, APM_CONTAINER_TYPE_GC} + @h2xmlm_isOffloadable {true} + @h2xmlm_stackSize { GAIN_STACK_SIZE } + @{ <-- Start of the Module --> + + @h2xml_Select {"param_id_module_enable_t"} + @h2xmlm_InsertParameter + +*/ + +/*============================================================================== + API definitions +==============================================================================*/ +/** @ingroup ar_spf_mod_gain_macros + ID of the parameter used to set the gain. */ +#define PARAM_ID_GAIN 0x08001006 + +/** @h2xmlp_parameter {"PARAM_ID_GAIN", + PARAM_ID_GAIN} + @h2xmlp_description {Configures the gain} + @h2xmlp_toolPolicy {Calibration; RTC} */ + +#include "spf_begin_pack.h" +/** @ingroup ar_spf_mod_gain_macros + Payload for parameter param_id_gain_cfg_t. */ +struct param_id_gain_cfg_t +{ + uint16_t gain; + /**< Linear gain (in Q13 format). */ + /**< @h2xmle_description {Linear gain (in Q13 format)} + @h2xmle_dataFormat {Q13} + @h2xmle_displayType {dbtextbox} + @h2xmle_default {0x2000} */ + + uint16_t reserved; + /**< Reserved. Clients must set this field to 0. */ + /**< @h2xmle_description {Clients must set this field to 0.} + @h2xmle_rangeList {"0"=0} + @h2xmle_visibility {hide} */ +} +#include "spf_end_pack.h" +; +/* Structure type def for above payload. */ +typedef struct param_id_gain_cfg_t param_id_gain_cfg_t; + +/** @} <-- End of the Module -->*/ + +#endif //_GAIN_API_H_ diff --git a/modules/processing/volume_control/capi/gain/build/CMakeLists.txt b/modules/processing/volume_control/capi/gain/build/CMakeLists.txt new file mode 100644 index 0000000..1a0279b --- /dev/null +++ b/modules/processing/volume_control/capi/gain/build/CMakeLists.txt @@ -0,0 +1,39 @@ +#[[ + @file CMakeLists.txt + + @brief + + @copyright + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause-Clear +]] +cmake_minimum_required(VERSION 3.10) + +set(gain_sources + ${LIB_ROOT}/src/capi_gain.c + ${LIB_ROOT}/src/capi_gain_utils.c + ../../../lib/src/apply_gain.c +) + +set(gain_includes + ${LIB_ROOT}/api + ${LIB_ROOT}/inc + ${LIB_ROOT}/src + ../../../lib/inc +) +spf_module_sources( + KCONFIG CONFIG_GAIN + NAME gain + MAJOR_VER 1 + MINOR_VER 0 + AMDB_ITYPE "capi" + AMDB_MTYPE "PP" + AMDB_MID "0x07001002" + AMDB_TAG "capi_gain" + AMDB_MOD_NAME "MODULE_ID_GAIN" + SRCS ${gain_sources} + INCLUDES ${gain_includes} + H2XML_HEADERS "${LIB_ROOT}/api/gain_api.h" + CFLAGS "" +) + diff --git a/modules/processing/volume_control/capi/gain/inc/capi_gain.h b/modules/processing/volume_control/capi/gain/inc/capi_gain.h new file mode 100644 index 0000000..01a8346 --- /dev/null +++ b/modules/processing/volume_control/capi/gain/inc/capi_gain.h @@ -0,0 +1,48 @@ +/* ======================================================================== */ +/** +@file capi_gain.h + + Header file to implement the Common Audio Post Processor Interface + for Tx/Rx Tuning Gain block +*/ + +/* ========================================================================= + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause-Clear + ========================================================================== */ + +/*------------------------------------------------------------------------ + * Include files + * -----------------------------------------------------------------------*/ +#ifndef CAPI_GAIN_H +#define CAPI_GAIN_H + +#include "capi.h" +#include "ar_defs.h" + +#ifdef __cplusplus +extern "C"{ +#endif /*__cplusplus*/ + +/** +* Get static properties of Gain module such as +* memory, stack requirements etc. +*/ +capi_err_t capi_gain_get_static_properties( + capi_proplist_t *init_set_properties, + capi_proplist_t *static_properties); + + +/** +* Instantiates(and allocates) the module memory. +*/ +capi_err_t capi_gain_init( + capi_t *_pif, + capi_proplist_t *init_set_properties); + +#ifdef __cplusplus +} +#endif /*__cplusplus*/ + +#endif //CAPI_GAIN_H + diff --git a/modules/processing/volume_control/capi/gain/src/capi_gain.c b/modules/processing/volume_control/capi/gain/src/capi_gain.c new file mode 100644 index 0000000..1b1e702 --- /dev/null +++ b/modules/processing/volume_control/capi/gain/src/capi_gain.c @@ -0,0 +1,297 @@ +/* ========================================================================= + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause-Clear + * =========================================================================*/ + +/** + * @file capi_gain.cpp + * + * C source file to implement the Gain module +*/ + +#include "capi_gain_utils.h" + +static capi_err_t capi_gain_process(capi_t * _pif, + capi_stream_data_t *input[], + capi_stream_data_t *output[]); + +static capi_err_t capi_gain_end(capi_t *_pif); + +static capi_err_t capi_gain_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_gain_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_gain_set_properties(capi_t *_pif, capi_proplist_t *props_ptr); + +static capi_err_t capi_gain_get_properties(capi_t *_pif, capi_proplist_t *props_ptr); + +static capi_vtbl_t vtbl = { capi_gain_process, capi_gain_end, + capi_gain_set_param, capi_gain_get_param, + capi_gain_set_properties, capi_gain_get_properties }; + +/* ------------------------------------------------------------------------- + * Function name: capi_gain_get_static_properties + * Capi_v2 Gain function to get the static properties + * -------------------------------------------------------------------------*/ +capi_err_t capi_gain_get_static_properties(capi_proplist_t *init_set_properties, + capi_proplist_t *static_properties) +{ + capi_err_t capi_result = CAPI_EOK; + GAIN_MSG(MIID_UNKNOWN, DBG_HIGH_PRIO, "Enter get static properties"); + if (NULL != static_properties) + { + capi_result = capi_gain_process_get_properties((capi_gain_t *)NULL, static_properties); + if (CAPI_FAILED(capi_result)) + { + GAIN_MSG(MIID_UNKNOWN, DBG_ERROR_PRIO, "get static properties failed!"); + return capi_result; + } + } + else + { + GAIN_MSG(MIID_UNKNOWN, DBG_ERROR_PRIO, "Get static properties received bad pointer, 0x%p", static_properties); + } + + return capi_result; +} + +/*------------------------------------------------------------------------ + Function name: capi_gain_init + Initialize the CAPIv2 Gain Module. This function can allocate memory. + * -----------------------------------------------------------------------*/ + +capi_err_t capi_gain_init(capi_t *_pif, capi_proplist_t *init_set_properties) +{ + capi_err_t capi_result = CAPI_EOK; + GAIN_MSG(MIID_UNKNOWN, DBG_LOW_PRIO, "Enter gain init"); + if (NULL == _pif || NULL == init_set_properties) + { + GAIN_MSG(MIID_UNKNOWN, DBG_ERROR_PRIO, "Init received bad pointer, 0x%p, 0x%p", _pif, init_set_properties); + + CAPI_SET_ERROR(capi_result, CAPI_EBADPARAM); + return capi_result; + } + + capi_gain_t *me_ptr = (capi_gain_t *)_pif; + + memset(me_ptr, 0, sizeof(capi_gain_t)); + + me_ptr->vtbl.vtbl_ptr = &vtbl; + + capi_cmn_init_media_fmt_v2(&me_ptr->inp_media_fmt); + + capi_gain_init_config(me_ptr); + + if (NULL != init_set_properties) + { + capi_result = capi_gain_process_set_properties(me_ptr, init_set_properties); + // ignore unsupported error + if (CAPI_FAILED(capi_result) && (CAPI_EUNSUPPORTED != capi_result)) + { + GAIN_MSG(me_ptr->miid, DBG_ERROR_PRIO, "Initialization Set Property Failed"); + return capi_result; + } + } + + capi_result |= capi_gain_raise_event(me_ptr); + + capi_result |= capi_cmn_raise_deinterleaved_unpacked_v2_supported_event(&me_ptr->cb_info); + + GAIN_MSG(me_ptr->miid, DBG_HIGH_PRIO, "Initialization completed !!"); + return capi_result; +} + +/* ------------------------------------------------------------------------- + * Function name: capi_gain_process + * Gain module Data Process function to process an input buffer + * and generates an output buffer. + * -------------------------------------------------------------------------*/ +static capi_err_t capi_gain_process(capi_t * _pif, + capi_stream_data_t *input[], + capi_stream_data_t *output[]) +{ + capi_err_t capi_result = CAPI_EOK; + capi_gain_t *me_ptr = (capi_gain_t *)_pif; + POSAL_ASSERT(me_ptr); + POSAL_ASSERT(input[0]); + POSAL_ASSERT(output[0]); + + capi_result = gain_process(_pif, input, output); + return capi_result; +} + +/*------------------------------------------------------------------------ + * Function name: capi_gain_end + * Gain End function, returns the library to the uninitialized + * state and frees all the memory that was allocated. This function also + * frees the virtual function table. + * -----------------------------------------------------------------------*/ +static capi_err_t capi_gain_end(capi_t *_pif) +{ + + capi_err_t capi_result = CAPI_EOK; + if (NULL == _pif) + { + GAIN_MSG(MIID_UNKNOWN, DBG_ERROR_PRIO, "End received bad pointer, 0x%p", _pif); + CAPI_SET_ERROR(capi_result, CAPI_EBADPARAM); + return capi_result; + } + + capi_gain_t *me_ptr = (capi_gain_t *)_pif; + uint32_t miid = me_ptr->miid; + me_ptr->vtbl.vtbl_ptr = NULL; + + GAIN_MSG(miid, DBG_HIGH_PRIO, "End done"); + return capi_result; +} + +/* ------------------------------------------------------------------------- + * Function name: capi_gain_set_param + * Sets either a parameter value or a parameter structure containing + * multiple parameters. In the event of a failure, the appropriate error + * code is returned. + * -------------------------------------------------------------------------*/ +static capi_err_t capi_gain_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 capi_result = CAPI_EOK; + if (NULL == _pif || NULL == params_ptr) + { + GAIN_MSG(MIID_UNKNOWN, DBG_ERROR_PRIO, "set param received bad pointer, 0x%p, 0x%p", _pif, params_ptr); + return CAPI_SET_ERROR(capi_result, CAPI_EBADPARAM); + } + capi_gain_t *me_ptr = (capi_gain_t *)(_pif); + + switch (param_id) + { + case PARAM_ID_MODULE_ENABLE: + { + uint16_t param_size = params_ptr->actual_data_len; + if (param_size >= sizeof(param_id_module_enable_t)) + { + param_id_module_enable_t *codec_gain_enable_ptr = (param_id_module_enable_t *)(params_ptr->data_ptr); + me_ptr->lib_config.enable = codec_gain_enable_ptr->enable; + capi_result |= capi_gain_raise_process_event(me_ptr); + GAIN_MSG(me_ptr->miid, DBG_HIGH_PRIO, "PARAM_ID_MODULE_ENABLE, %lu", me_ptr->lib_config.enable); + } + else + { + GAIN_MSG(me_ptr->miid, DBG_ERROR_PRIO, "CAPIv2 Codec Gain Enable/Disable, Bad param size %hu", param_size); + CAPI_SET_ERROR(capi_result, CAPI_ENEEDMORE); + } + break; + } + case PARAM_ID_GAIN: + { + if (params_ptr->actual_data_len >= sizeof(param_id_gain_cfg_t)) + { + param_id_gain_cfg_t *rx_gain_packet_ptr = (param_id_gain_cfg_t *)(params_ptr->data_ptr); + me_ptr->lib_config.gain_q13 = rx_gain_packet_ptr->gain; + me_ptr->gain_q12 = rx_gain_packet_ptr->gain >> 1; + capi_result |= capi_gain_raise_process_event(me_ptr); + GAIN_MSG(me_ptr->miid, DBG_HIGH_PRIO, "PARAM_ID_GAIN %d", me_ptr->lib_config.gain_q13); + } + else + { + GAIN_MSG(me_ptr->miid, DBG_ERROR_PRIO, "Set, Bad param size %lu", params_ptr->actual_data_len); + CAPI_SET_ERROR(capi_result, CAPI_ENEEDMORE); + } + break; + } + default: + { + GAIN_MSG(me_ptr->miid, DBG_ERROR_PRIO, "Set, unsupported param ID 0x%x", (int)param_id); + CAPI_SET_ERROR(capi_result, CAPI_EUNSUPPORTED); + break; + } + } + return capi_result; +} + +/* ------------------------------------------------------------------------- + * Function name: capi_gain_get_param + * Gets either a parameter value or a parameter structure containing + * multiple parameters. In the event of a failure, the appropriate error + * code is returned. + * -------------------------------------------------------------------------*/ +static capi_err_t capi_gain_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 capi_result = CAPI_EOK; + if (NULL == _pif || NULL == params_ptr) + { + GAIN_MSG(MIID_UNKNOWN, DBG_ERROR_PRIO, "Get param received bad pointer, 0x%p, 0x%p", _pif, params_ptr); + return CAPI_EBADPARAM; + } + + capi_gain_t *me_ptr = (capi_gain_t *)_pif; + + switch (param_id) + { + case PARAM_ID_MODULE_ENABLE: + { + const uint16_t payload_size = (uint16_t)(params_ptr->max_data_len); + if (payload_size >= sizeof(param_id_module_enable_t)) + { + param_id_module_enable_t *codec_gain_enable_ptr = (param_id_module_enable_t *)(params_ptr->data_ptr); + codec_gain_enable_ptr->enable = me_ptr->lib_config.enable; + params_ptr->actual_data_len = (uint32_t)sizeof(param_id_module_enable_t); + } + else + { + GAIN_MSG(me_ptr->miid, DBG_ERROR_PRIO, "CAPIv2 Codec Gain Get Enable Param, Bad payload size %d", payload_size); + CAPI_SET_ERROR(capi_result, CAPI_ENEEDMORE); + } + break; + } + case PARAM_ID_GAIN: + { + if (params_ptr->max_data_len >= sizeof(param_id_gain_cfg_t)) + { + param_id_gain_cfg_t *rx_gain_packet_ptr = (param_id_gain_cfg_t *)(params_ptr->data_ptr); + params_ptr->actual_data_len = sizeof(param_id_gain_cfg_t); + rx_gain_packet_ptr->gain = me_ptr->lib_config.gain_q13; + rx_gain_packet_ptr->reserved = 0; + } + else + { + GAIN_MSG(me_ptr->miid, DBG_ERROR_PRIO, "Get, Bad param size %lu", params_ptr->max_data_len); + CAPI_SET_ERROR(capi_result, CAPI_ENEEDMORE); + } + break; + } + default: + { + GAIN_MSG(me_ptr->miid, DBG_ERROR_PRIO, "Get, unsupported param ID 0x%x", (int)param_id); + CAPI_SET_ERROR(capi_result, CAPI_EUNSUPPORTED); + break; + } + } + return capi_result; +} + +static capi_err_t capi_gain_set_properties(capi_t *_pif, capi_proplist_t *props_ptr) +{ + capi_gain_t *me_ptr = (capi_gain_t *)_pif; + return capi_gain_process_set_properties(me_ptr, props_ptr); +} + +/* ------------------------------------------------------------------------- + * Function name: capi_gain_get_properties + * Function to get the properties of GAIN module + * -------------------------------------------------------------------------*/ +static capi_err_t capi_gain_get_properties(capi_t *_pif, capi_proplist_t *props_ptr) +{ + capi_gain_t *me_ptr = (capi_gain_t *)_pif; + return capi_gain_process_get_properties(me_ptr, props_ptr); +} diff --git a/modules/processing/volume_control/capi/gain/src/capi_gain_utils.c b/modules/processing/volume_control/capi/gain/src/capi_gain_utils.c new file mode 100644 index 0000000..e850ea6 --- /dev/null +++ b/modules/processing/volume_control/capi/gain/src/capi_gain_utils.c @@ -0,0 +1,417 @@ +/* ========================================================================= + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause-Clear + * =========================================================================*/ + +/** + * @file capi_gain_utils.cpp + * + * C source file to implement the Audio Post Processor Interface for + * Gain Module + */ + +#include "capi_gain_utils.h" + + +static bool_t gain_is_supported_media_type(const capi_media_fmt_v2_t *format_ptr); + +static bool_t gain_is_supported_media_type(const capi_media_fmt_v2_t *format_ptr) +{ + if (CAPI_FIXED_POINT != format_ptr->header.format_header.data_format) + { + AR_MSG(DBG_ERROR_PRIO, + "CAPI GAIN: unsupported data format %lu", + (uint32_t)format_ptr->header.format_header.data_format); + return FALSE; + } + + if ((16 != format_ptr->format.bits_per_sample) && (32 != format_ptr->format.bits_per_sample)) + { + AR_MSG(DBG_ERROR_PRIO, + "CAPI GAIN: only supports 16 and 32 bit data. Received %lu.", + format_ptr->format.bits_per_sample); + return FALSE; + } + + if ((CAPI_DEINTERLEAVED_UNPACKED_V2 != format_ptr->format.data_interleaving) && + (CAPI_INTERLEAVED != format_ptr->format.data_interleaving) && + (format_ptr->format.num_channels != 1)) + { + AR_MSG(DBG_ERROR_PRIO, "CAPI GAIN : Interleaved data not supported."); + return FALSE; + } + + if (!format_ptr->format.data_is_signed) + { + AR_MSG(DBG_ERROR_PRIO, "CAPI GAIN: Unsigned data not supported."); + return FALSE; + } + + if ((format_ptr->format.num_channels == 0) || (format_ptr->format.num_channels > CAPI_MAX_CHANNELS_V2)) + { + AR_MSG(DBG_ERROR_PRIO, + "CAPIv2 Gain: Only upto %lu channels supported." + "Received %lu.", + CAPI_MAX_CHANNELS_V2, + format_ptr->format.num_channels); + return FALSE; + } + + return TRUE; +} + +void capi_gain_init_config(capi_gain_t *me_ptr) +{ + me_ptr->events_config.enable = TRUE; + me_ptr->lib_config.enable = TRUE; + + uint16_t apply_gain = 0x2000; + me_ptr->gain_q12 = apply_gain >> 1; + me_ptr->lib_config.gain_q13 = apply_gain; +} + +/* ========================================================================= + * FUNCTION : capi_gain_raise_process_event + * DESCRIPTION: Function to send the output media format using the + * callback function + * =========================================================================*/ +capi_err_t capi_gain_raise_process_event(capi_gain_t *me_ptr) +{ + capi_err_t capi_result = CAPI_EOK; + uint32_t enable = TRUE; + if (Q13_UNITY == me_ptr->gain_q12 << 1) + { + enable = FALSE; + } + enable = enable && ((me_ptr->lib_config.enable == 0) ? 0 : 1); + + if (me_ptr->events_config.enable != enable) + { + capi_result = capi_cmn_update_process_check_event(&me_ptr->cb_info, enable); + if (CAPI_EOK == capi_result) + { + me_ptr->events_config.enable = enable; + } + } + return capi_result; +} + +/* ========================================================================= + * FUNCTION : capi_gain_update_event_states + * DESCRIPTION: function to get KPPS numbers + * =========================================================================*/ +static uint32_t capi_gain_get_kpps(capi_gain_t *me_ptr) +{ + uint32_t kpps = 0; + + if ((me_ptr->inp_media_fmt.format.sampling_rate != CAPI_DATA_FORMAT_INVALID_VAL) && + (me_ptr->inp_media_fmt.format.num_channels != CAPI_DATA_FORMAT_INVALID_VAL)) + { + uint32_t kpps_8khz_mono = CAPI_GAIN_KPPS_8KHZ_MONO_CH_16BIT; + if (BIT_WIDTH_32 == me_ptr->inp_media_fmt.format.bits_per_sample) + { + if ((uint16_t)(me_ptr->gain_q12 >> 12) > 0) + { + // kpps for gain greater than 1 + kpps_8khz_mono = CAPI_GAIN_KPPS_8KHZ_MONO_CH_32BIT_G1; + } + else + { + // kpps for gain less than 1 + kpps_8khz_mono = CAPI_GAIN_KPPS_8KHZ_MONO_CH_32BIT_L1; + } + } + // scale KPPS based on num channels and sampling rate. + kpps = kpps_8khz_mono * me_ptr->inp_media_fmt.format.num_channels * + (me_ptr->inp_media_fmt.format.sampling_rate / 8000); + } + return kpps; +} + +/* ========================================================================= + * FUNCTION : capi_gain_raise_event + * DESCRIPTION: Function to raise various events of the gain module + * =========================================================================*/ +capi_err_t capi_gain_raise_event(capi_gain_t *me_ptr) +{ + capi_err_t capi_result = CAPI_EOK; + if (NULL == me_ptr->cb_info.event_cb) + { + GAIN_MSG(me_ptr->miid, DBG_ERROR_PRIO, "Event callback is not set. Unable to raise events!"); + CAPI_SET_ERROR(capi_result, CAPI_EUNSUPPORTED); + return capi_result; + } + + me_ptr->events_config.kpps = capi_gain_get_kpps(me_ptr); + me_ptr->events_config.delay_in_us = 0; + + capi_result |= capi_gain_raise_process_event(me_ptr); + capi_result |= capi_cmn_update_kpps_event(&me_ptr->cb_info, me_ptr->events_config.kpps); + capi_result |= capi_cmn_update_bandwidth_event(&me_ptr->cb_info, + me_ptr->events_config.code_bw, + me_ptr->events_config.data_bw); + capi_result |= capi_cmn_update_algo_delay_event(&me_ptr->cb_info, me_ptr->events_config.delay_in_us); + + return capi_result; +} + +/*------------------------------------------------------------------------ + Function name: gain_process + Processes an input buffer and generates an output buffer. + * -----------------------------------------------------------------------*/ +capi_err_t gain_process(capi_t *_pif, capi_stream_data_t *input[], capi_stream_data_t *output[]) +{ + capi_err_t capi_result = CAPI_EOK; + capi_gain_t *me_ptr = (capi_gain_t *)_pif; + + uint32_t nSampleCnt; + int32_t byte_sample_convert = (BIT_WIDTH_16 == me_ptr->inp_media_fmt.format.bits_per_sample) ? 1 : 2; + nSampleCnt = s32_min_s32_s32(input[0]->buf_ptr[0].actual_data_len >> byte_sample_convert, + output[0]->buf_ptr[0].max_data_len >> byte_sample_convert); + + uint32_t num_bytes_processed = 0; + for (uint32_t ch = 0; ch < input[0]->bufs_num; ch++) + { + if (BIT_WIDTH_16 == me_ptr->inp_media_fmt.format.bits_per_sample) + { + audpp_volume_apply_gain_16((int16 *)output[0]->buf_ptr[ch].data_ptr, + (int16 *)input[0]->buf_ptr[ch].data_ptr, + me_ptr->gain_q12, + nSampleCnt); + num_bytes_processed = nSampleCnt << 1; + } + else + { + if ((uint16)(me_ptr->gain_q12 >> 12) > 0) + { + // for gain greater than 1 + audpp_volume_apply_gain_32_G1((int32 *)output[0]->buf_ptr[ch].data_ptr, + (int32 *)input[0]->buf_ptr[ch].data_ptr, + me_ptr->gain_q12, + nSampleCnt); + } + else + { + // for gain less than 1 + audpp_volume_apply_gain_32_L1((int32 *)output[0]->buf_ptr[ch].data_ptr, + (int32 *)input[0]->buf_ptr[ch].data_ptr, + me_ptr->gain_q12, + nSampleCnt); + } + num_bytes_processed = nSampleCnt << 2; + } + } + + // update rest of the channels since module supports only CAPI_DEINTERLEAVED_UNPACKED_V2 + output[0]->buf_ptr[0].actual_data_len = num_bytes_processed; + input[0]->buf_ptr[0].actual_data_len = num_bytes_processed; + + return capi_result; +} + +capi_err_t capi_gain_process_set_properties(capi_gain_t *me_ptr, capi_proplist_t *proplist_ptr) +{ + capi_err_t capi_result = CAPI_EOK; + + if (NULL == me_ptr) + { + GAIN_MSG(MIID_UNKNOWN, DBG_ERROR_PRIO, "Set common property received null ptr"); + return CAPI_EBADPARAM; + } + + capi_result |= capi_cmn_set_basic_properties(proplist_ptr, &me_ptr->heap_info, &me_ptr->cb_info, TRUE); + uint32_t miid = me_ptr ? me_ptr->miid : MIID_UNKNOWN; + if (CAPI_EOK != capi_result) + { + GAIN_MSG(miid, DBG_ERROR_PRIO, "Set basic properties failed with result %lu", capi_result); + } + + capi_prop_t *prop_array = proplist_ptr->prop_ptr; + uint32_t i = 0; + + for (i = 0; i < proplist_ptr->props_num; i++) + { + capi_buf_t *payload_ptr = &(prop_array[i].payload); + miid = me_ptr ? me_ptr->miid : MIID_UNKNOWN; + + switch (prop_array[i].id) + { + case CAPI_EVENT_CALLBACK_INFO: + case CAPI_HEAP_ID: + case CAPI_ALGORITHMIC_RESET: + case CAPI_CUSTOM_INIT_DATA: + case CAPI_PORT_NUM_INFO: + case CAPI_INTERFACE_EXTENSIONS: + case CAPI_OUTPUT_MEDIA_FORMAT_V2: + { + break; + } + case CAPI_INPUT_MEDIA_FORMAT_V2: + { + if (payload_ptr->actual_data_len >= sizeof(capi_media_fmt_v2_t)) + { + GAIN_MSG(miid, DBG_HIGH_PRIO, "Received Input media format"); + + capi_media_fmt_v2_t *data_ptr = (capi_media_fmt_v2_t *)(payload_ptr->data_ptr); + if (!gain_is_supported_media_type(data_ptr)) + { + CAPI_SET_ERROR(capi_result, CAPI_EFAILED); + return capi_result; + } + + // copy and save the input media fmt + me_ptr->inp_media_fmt.header.format_header.data_format = data_ptr->header.format_header.data_format; + me_ptr->inp_media_fmt.format = data_ptr->format; + + for (uint32_t ch = 0; ch < me_ptr->inp_media_fmt.format.num_channels; ch++) + { + me_ptr->inp_media_fmt.channel_type[ch] = data_ptr->channel_type[ch]; + } + + // raise event for output media format + capi_result |= capi_gain_raise_event(me_ptr); + capi_result |= + capi_cmn_output_media_fmt_event_v2(&me_ptr->cb_info, &me_ptr->inp_media_fmt, FALSE, 0); + } + else + { + GAIN_MSG(miid, DBG_ERROR_PRIO, + "Set property id 0x%lx Bad param size %lu", + (uint32_t)prop_array[i].id, + payload_ptr->actual_data_len); + CAPI_SET_ERROR(capi_result, CAPI_ENEEDMORE); + } + 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; + GAIN_MSG(miid, DBG_LOW_PRIO, + "This module-id 0x%08lX, instance-id 0x%08lX", + data_ptr->module_id, + me_ptr->miid); + } + else + { + GAIN_MSG(miid, DBG_ERROR_PRIO, + "Set property id 0x%lx, Bad param size %lu", + prop_array[i].id, + payload_ptr->max_data_len); + capi_result |= CAPI_ENEEDMORE; + } + break; + } // CAPI_MODULE_INSTANCE_ID + default: + { + capi_result |= CAPI_EUNSUPPORTED; + break; + } + } + if (CAPI_FAILED(capi_result)) + { + GAIN_MSG(miid, DBG_HIGH_PRIO, + "Set property for %#x failed with opcode %lu", + prop_array[i].id, + capi_result); + } + } + + return capi_result; +} + +capi_err_t capi_gain_process_get_properties(capi_gain_t *me_ptr, capi_proplist_t *proplist_ptr) +{ + capi_err_t capi_result = CAPI_EOK; + + capi_basic_prop_t mod_prop; + mod_prop.init_memory_req = align_to_8_byte(sizeof(capi_gain_t)); + mod_prop.stack_size = GAIN_STACK_SIZE; + mod_prop.num_fwk_extns = 0; + mod_prop.fwk_extn_ids_arr = NULL; + mod_prop.is_inplace = TRUE; + mod_prop.req_data_buffering = FALSE; + mod_prop.max_metadata_size = 0; + + uint32_t miid = me_ptr ? me_ptr->miid : MIID_UNKNOWN; + + capi_result |= capi_cmn_get_basic_properties(proplist_ptr, &mod_prop); + if (CAPI_EOK != capi_result) + { + GAIN_MSG(miid, DBG_ERROR_PRIO, "Get common basic properties failed with result %lu", capi_result); + } + + capi_prop_t *prop_array = proplist_ptr->prop_ptr; + + for (uint32_t i = 0; i < proplist_ptr->props_num; i++) + { + miid = me_ptr ? me_ptr->miid : MIID_UNKNOWN; + switch (prop_array[i].id) + { + case CAPI_INIT_MEMORY_REQUIREMENT: + case CAPI_STACK_SIZE: + case CAPI_IS_INPLACE: + case CAPI_REQUIRES_DATA_BUFFERING: + case CAPI_OUTPUT_MEDIA_FORMAT_SIZE: + case CAPI_NUM_NEEDED_FRAMEWORK_EXTENSIONS: + { + break; + } + case CAPI_IS_ELEMENTARY: + { + capi_buf_t *payload_ptr = &prop_array[i].payload; + if (payload_ptr->max_data_len >= sizeof(capi_is_elementary_t)) + { + capi_is_elementary_t *data_ptr = (capi_is_elementary_t *)payload_ptr->data_ptr; + data_ptr->is_elementary = TRUE; + payload_ptr->actual_data_len = sizeof(capi_is_elementary_t); + } + else + { + GAIN_MSG(miid, DBG_ERROR_PRIO, + "Get basic property id 0x%lx Bad param size %lu", + (uint32_t)prop_array[i].id, + payload_ptr->max_data_len); + CAPI_SET_ERROR(capi_result, CAPI_ENEEDMORE); + } + break; + } + case CAPI_OUTPUT_MEDIA_FORMAT_V2: + { + if (NULL == me_ptr) + { + GAIN_MSG(MIID_UNKNOWN, DBG_ERROR_PRIO, "null ptr while querying output mf"); + return CAPI_EBADPARAM; + } + capi_result = capi_cmn_handle_get_output_media_fmt_v2(&prop_array[i], &me_ptr->inp_media_fmt); + break; + } + case CAPI_PORT_DATA_THRESHOLD: + { + if (NULL == me_ptr) + { + GAIN_MSG(MIID_UNKNOWN, DBG_ERROR_PRIO, "null ptr while querying threshold"); + return CAPI_EBADPARAM; + } + uint32_t threshold_in_bytes = 1; // default + capi_result = capi_cmn_handle_get_port_threshold(&prop_array[i], threshold_in_bytes); + break; + } + default: + { + capi_result |= CAPI_EUNSUPPORTED; + break; + } + } + if (CAPI_FAILED(capi_result)) + { + GAIN_MSG(miid, DBG_HIGH_PRIO, + "Get property for %#x failed with opcode %lu", + prop_array[i].id, + capi_result); + } + } + return capi_result; +} diff --git a/modules/processing/volume_control/capi/gain/src/capi_gain_utils.h b/modules/processing/volume_control/capi/gain/src/capi_gain_utils.h new file mode 100644 index 0000000..e3856a8 --- /dev/null +++ b/modules/processing/volume_control/capi/gain/src/capi_gain_utils.h @@ -0,0 +1,96 @@ +/* ======================================================================== */ +/** +@file capiv2_gain_utils.h + + Header file to implement the Gain block +*/ + +/* ========================================================================= + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause-Clear + ========================================================================== */ + +/*------------------------------------------------------------------------ + * Include files + * -----------------------------------------------------------------------*/ + +#ifndef CAPI_GAIN_UTILS_H +#define CAPI_GAIN_UTILS_H + +#include "gain_api.h" +#include "capi_gain.h" +#include "capi_cmn.h" +#include "audpp_util.h" +#include "apply_gain.h" + +#define GAIN_BW (1*1024*1024); + +// KPPS number for 8KHz sampling rate and mono channel +//For now the values are same +#define CAPI_GAIN_KPPS_8KHZ_MONO_CH_16BIT (30) + +#define CAPI_GAIN_KPPS_8KHZ_MONO_CH_32BIT_G1 (30) +#define CAPI_GAIN_KPPS_8KHZ_MONO_CH_32BIT_L1 (30) + + +/* debug message */ +#define MIID_UNKNOWN 0 +#define GAIN_MSG_PREFIX "CAPI GAIN:[%lX] " +#define GAIN_MSG(ID, xx_ss_mask, xx_fmt, ...)\ + AR_MSG(xx_ss_mask, GAIN_MSG_PREFIX xx_fmt, ID, ##__VA_ARGS__) + +/*------------------------------------------------------------------------ + * Function declarations + * -----------------------------------------------------------------------*/ +typedef struct capi_gain_events_config +{ + uint32_t enable; + uint32_t kpps; + uint32_t delay_in_us; + uint32_t code_bw; + uint32_t data_bw; +} capi_gain_events_config_t; + +typedef struct capi_gain_module_config +{ + uint32_t enable; + uint16_t gain_q13; +} capi_gain_module_config_t; + +typedef struct capi_gain_t +{ + capi_t vtbl; + capi_event_callback_info_t cb_info; + capi_heap_id_t heap_info; + capi_media_fmt_v2_t inp_media_fmt; + capi_gain_events_config_t events_config; + capi_gain_module_config_t lib_config; + uint16_t gain_q12; ///< gain level at Q12 + uint32_t miid; +} capi_gain_t; + +capi_err_t capi_gain_process_set_properties( + capi_gain_t *me_ptr, + capi_proplist_t *proplist_ptr); + +capi_err_t capi_gain_process_get_properties( + capi_gain_t *me_ptr, + capi_proplist_t *proplist_ptr); + +void capi_gain_init_config( + capi_gain_t *me_ptr); + +capi_err_t gain_process( + capi_t* _pif, + capi_stream_data_t* input[], + capi_stream_data_t* output[]); + +capi_err_t capi_gain_raise_event( + capi_gain_t *me_ptr); + +capi_err_t capi_gain_raise_process_event( + capi_gain_t *me_ptr); + +#endif //CAPI_GAIN_UTILS_H + + diff --git a/modules/processing/volume_control/capi/gain/stub_src/capi_gain_stub.cpp b/modules/processing/volume_control/capi/gain/stub_src/capi_gain_stub.cpp new file mode 100644 index 0000000..be956a4 --- /dev/null +++ b/modules/processing/volume_control/capi/gain/stub_src/capi_gain_stub.cpp @@ -0,0 +1,28 @@ +/* ========================================================================= + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause-Clear + * =========================================================================*/ + +/** + * @file capi_gain_stub.cpp + * + * Stub Interface for gain. + */ + +#include "capi_gain.h" + +capi_err_t capi_gain_get_static_properties( + capi_proplist_t *init_set_properties, + capi_proplist_t *static_properties) +{ + return CAPI_EUNSUPPORTED; +} + + +capi_err_t capi_gain_init( + capi_t *_pif, + capi_proplist_t *init_set_properties) +{ + return CAPI_EUNSUPPORTED; +} + diff --git a/modules/processing/volume_control/capi/soft_vol/api/soft_vol_api.h b/modules/processing/volume_control/capi/soft_vol/api/soft_vol_api.h new file mode 100644 index 0000000..6d5f748 --- /dev/null +++ b/modules/processing/volume_control/capi/soft_vol/api/soft_vol_api.h @@ -0,0 +1,561 @@ +#ifndef _SOFT_VOL_API_H_ +#define _SOFT_VOL_API_H_ + +/*============================================================================== + @file soft_vol_api.h + @brief This file contains Soft Vol Module 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_p_eq_vol_api.h" +#include "imcl_module_gain_api.h" +#include "imcl_mute_api.h" + +/** + @h2xml_title1 {Volume Control Module API} + @h2xml_title_agile_rev {Volume Control Module API} + @h2xml_title_date {Aug 22, 2018} */ + +/*============================================================================== + Defines +==============================================================================*/ +/** Supported parameters for a soft stepping linear ramping curve. */ +#define PARAM_VOL_CTRL_RAMPINGCURVE_LINEAR 0 + +/** Exponential ramping curve. */ +#define PARAM_VOL_CTRL_RAMPINGCURVE_EXP 1 + +/** Logarithmic ramping curve. */ +#define PARAM_VOL_CTRL_RAMPINGCURVE_LOG 2 + +/** Fractional exponent ramping curve.*/ +#define PARAM_VOL_CTRL_RAMPINGCURVE_FRAC_EXP 3 + +#ifdef PROD_SPECIFIC_MAX_CH +#define VOLUME_CONTROL_MAX_CHANNELS 128 +#else +#define VOLUME_CONTROL_MAX_CHANNELS 32 +#endif + +/** Module ID for SOFT VOL module */ +#define MODULE_ID_VOL_CTRL 0x0700101B + +/* Input port ID of Vol Ctrl module */ +#define VOL_CTRL_DATA_INPUT_PORT 0x2 + +/* Output port ID of Vol Ctrl module */ +#define VOL_CTRL_DATA_OUTPUT_PORT 0x1 + +/*------------------------------------------------------------------------------ + Module +------------------------------------------------------------------------------*/ +/** + @h2xmlm_module {"MODULE_ID_VOL_CTRL", + MODULE_ID_VOL_CTRL} + @h2xmlm_displayName {"Volume Control"} + @h2xmlm_modSearchKeys{gain, Audio} + @h2xmlm_description {- ID of the volume control module.\n + - This module supports the following parameter IDs:\n + - #PARAM_ID_VOL_CTRL_MASTER_GAIN\n + - #PARAM_ID_VOL_CTRL_MASTER_MUTE\n + - #PARAM_ID_VOL_CTRL_GAIN_RAMP_PARAMETERS\n + - #PARAM_ID_VOL_CTRL_MUTE_RAMP_PARAMETERS\n + - #PARAM_ID_VOL_CTRL_MULTICHANNEL_GAIN\n + - #PARAM_ID_VOL_CTRL_MULTICHANNEL_MUTE\n + - Supported Input Media Format: \n + - Data Format : FIXED \n + - fmt_id : Don't care \n + - Sample Rates : Don't care \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 : Don't care \n + - Interleaving : de-interleaved unpacked \n + - Signed/unsigned : Signed \n + -All parameter IDs are device independent.\n} + + @h2xmlm_dataMaxInputPorts { 1 } + @h2xmlm_dataInputPorts { IN = VOL_CTRL_DATA_INPUT_PORT} + @h2xmlm_dataOutputPorts { OUT= VOL_CTRL_DATA_OUTPUT_PORT} + @h2xmlm_dataMaxOutputPorts { 1 } + @h2xmlm_supportedContTypes {APM_CONTAINER_TYPE_SC, APM_CONTAINER_TYPE_GC} + @h2xmlm_isOffloadable {true} + @h2xmlm_stackSize { 2048 } + @h2xmlm_ctrlDynamicPortIntent { "Gain Info IMCL" = INTENT_ID_GAIN_INFO, maxPorts= INFINITE } + @h2xmlm_ctrlDynamicPortIntent { "Mute IMCL" = INTENT_ID_MUTE, maxPorts= INFINITE } + @h2xmlm_ctrlDynamicPortIntent { "Popless Equalizer to Soft Volume for headroom control" = INTENT_ID_P_EQ_VOL_HEADROOM, maxPorts= 1 } + @{ <-- Start of the Module --> */ + +/*============================================================================== + API definitions +==============================================================================*/ +/* Param-id for communicating headroom requirement to the Volume Control module through + * the service. Currently, this is used only by the Popless Equalizer by raising CAPI_EVENT_HEADROOM event */ +#ifndef PARAM_ID_ALGORITHMIC_HEADROOM +#define PARAM_ID_ALGORITHMIC_HEADROOM (0x0800103A) +#endif + +/* ID of the Master Gain parameter used by MODULE_ID_VOL_CTRL. */ +#define PARAM_ID_VOL_CTRL_MASTER_GAIN 0x08001035 + +/** @h2xmlp_parameter {"PARAM_ID_VOL_CTRL_MASTER_GAIN", PARAM_ID_VOL_CTRL_MASTER_GAIN} + @h2xmlp_description {Specifies the master gain} + @h2xmlp_toolPolicy {Calibration; RTC}*/ + +/* Payload of the PARAM_ID_VOL_CTRL_MASTER_GAIN parameter used + by the Volume Control module */ + /* Structure for the master gain parameter for a volume control module. */ +#include "spf_begin_pack.h" +struct volume_ctrl_master_gain_t +{ + uint16_t master_gain; +/**< @h2xmle_description {Specifies linear master gain in Q13 format\n} + @h2xmle_dataFormat {Q13} + @h2xmle_default {0x2000} */ + + uint16_t reserved; +/**< @h2xmle_description {Clients must set this field to 0.\n} + @h2xmle_rangeList {"0" = 0} + @h2xmle_default {0} */ +} +#include "spf_end_pack.h" +; + +/* Structure type def for above payload. */ +typedef struct volume_ctrl_master_gain_t volume_ctrl_master_gain_t; + + + +/** ID of the mute Configuration parameter used by MODULE_ID_VOL_CTRL. */ +#define PARAM_ID_VOL_CTRL_MASTER_MUTE 0x08001036 +/** @h2xmlp_parameter {"PARAM_ID_VOL_CTRL_MASTER_MUTE", PARAM_ID_VOL_CTRL_MASTER_MUTE} + @h2xmlp_description {Configures the mute flag} + @h2xmlp_toolPolicy {Calibration; RTC}*/ + +#include "spf_begin_pack.h" + +/* Payload of the PARAM_ID_VOL_CTRL_MASTER_MUTE parameter used + by the Volume Control module */ + /* Structure for the mute configuration parameter for a + volume control module. */ +struct volume_ctrl_master_mute_t +{ + uint32_t mute_flag; +/**< @h2xmle_description {Specifies whether mute is enabled} + @h2xmle_rangeList {"Disable"= 0; + "Enable"=1} + @h2xmle_default {0} */ +} +#include "spf_end_pack.h" +; + +/* Structure type def for above payload. */ +typedef struct volume_ctrl_master_mute_t volume_ctrl_master_mute_t; + + + +/* ID of the Soft Stepping gain ramp parameters used by MODULE_ID_VOL_CTRL. */ +#define PARAM_ID_VOL_CTRL_GAIN_RAMP_PARAMETERS 0x08001037 +/** @h2xmlp_parameter {"PARAM_ID_VOL_CTRL_GAIN_RAMP_PARAMETERS", PARAM_ID_VOL_CTRL_GAIN_RAMP_PARAMETERS} + @h2xmlp_toolPolicy {RTC; Calibration} + @h2xmlp_description {Specifies Soft Stepping gain ramp parameters} */ + + +/* Structure for holding soft stepping volume parameters. */ +#include "spf_begin_pack.h" +struct volume_ctrl_gain_ramp_params_t +{ + uint32_t period_ms; +/**< @h2xmle_description { Specifies period in milliseconds} + @h2xmle_range {0..15000} + @h2xmle_default {0} */ + + uint32_t step_us; +/**< @h2xmle_description { Specifies step in microseconds} + @h2xmle_range {0..15000000} + @h2xmle_default {0} */ + + uint32_t ramping_curve; +/**< @h2xmle_description {Specifies ramping curve type.\n + -Supported Values for ramping curve:\n + ->Linear ramping curve\n + -PARAM_VOL_CTRL_RAMPINGCURVE_LINEAR - 0 \n + ->Exponential ramping curve\n + -PARAM_VOL_CTRL_RAMPINGCURVE_EXP - 1 \n + ->Logarithmic ramping curve\n + -PARAM_VOL_CTRL_RAMPINGCURVE_LOG - 2 \n + ->Fractional exponent ramping curve\n + -PARAM_VOL_CTRL_RAMPINGCURVE_FRAC_EXP - 3 } + + @h2xmle_rangeList {"PARAM_VOL_CTRL_RAMPINGCURVE_LINEAR" = 0; + "PARAM_VOL_CTRL_RAMPINGCURVE_EXP" = 1; + "PARAM_VOL_CTRL_RAMPINGCURVE_LOG" = 2; + "PARAM_VOL_CTRL_RAMPINGCURVE_FRAC_EXP" = 3} + @h2xmle_default {0} */ +} +#include "spf_end_pack.h" +; + +/* Structure type def for above payload. */ +typedef struct volume_ctrl_gain_ramp_params_t volume_ctrl_gain_ramp_params_t; + + + +/* ID of the Soft Stepping mute ramp parameters used by MODULE_ID_VOL_CTRL. */ +#define PARAM_ID_VOL_CTRL_MUTE_RAMP_PARAMETERS 0x0800103D +/** @h2xmlp_parameter {"PARAM_ID_VOL_CTRL_MUTE_RAMP_PARAMETERS", PARAM_ID_VOL_CTRL_MUTE_RAMP_PARAMETERS} + @h2xmlp_description {Specifies Soft Stepping mute ramp parameters } + @h2xmlp_toolPolicy {RTC;Calibration} */ + +/* Structure for holding soft stepping volume parameters. */ +#include "spf_begin_pack.h" +struct volume_ctrl_mute_ramp_params_t +{ + uint32_t period_ms; +/**< @h2xmle_description { Specifies period in milliseconds.\n } + @h2xmle_range {0..15000} + @h2xmle_default {0} */ + + uint32_t step_us; +/**< @h2xmle_description { Specifies step in microseconds.\n} + @h2xmle_range {0..15000000} + @h2xmle_default {0} */ + + uint32_t ramping_curve; +/**< @h2xmle_description { Specifies ramping curve type.\n + -Supported Values for ramping curve:\n + ->Linear ramping curve\n + -PARAM_VOL_CTRL_RAMPINGCURVE_LINEAR - 0 \n + ->Exponential ramping curve\n + -PARAM_VOL_CTRL_RAMPINGCURVE_EXP - 1 \n + ->Logarithmic ramping curve\n + -PARAM_VOL_CTRL_RAMPINGCURVE_LOG - 2 \n + ->Fractional exponent ramping curve\n + -PARAM_VOL_CTRL_RAMPINGCURVE_FRAC_EXP - 3 } + + @h2xmle_rangeList {"PARAM_VOL_CTRL_RAMPINGCURVE_LINEAR" = 0; + "PARAM_VOL_CTRL_RAMPINGCURVE_EXP" = 1; + "PARAM_VOL_CTRL_RAMPINGCURVE_LOG" = 2; + "PARAM_VOL_CTRL_RAMPINGCURVE_FRAC_EXP" = 3} + @h2xmle_default {0} */ +} +#include "spf_end_pack.h" +; + +/* Structure type def for above payload. */ +typedef struct volume_ctrl_mute_ramp_params_t volume_ctrl_mute_ramp_params_t; + + + +/* Structure for holding one channel type - gain pair. */ +/** @h2xmlp_subStruct */ +#include "spf_begin_pack.h" +struct volume_ctrl_channels_gain_config_t +{ + uint32_t channel_mask_lsb; + /**< @h2xmle_description {Lower 32 bits of the channel mask. Each bit corresponds to channel map + from 1 (PCM_CHANNEL_L) to 31 (PCM_CHANNEL_LW). + Bit 0 is reserved and must be set to zero.\n + -#A set bit indicates that the gain is set on the corresponding channel-maps. + Bit position of the channel-map is obtained by left shifting (1 (left shift) Channel_map) + } + @h2xmle_default {0xFFFFFFFE} */ + + + uint32_t channel_mask_msb; + /**< @h2xmle_description {Upper 32 bits of the channel mask. Each bit corresponds to channel map + from 32 (PCM_CHANNEL_RW) to 63 (PCM_CUSTOM_CHANNEL_MAP_16).\n + -#A set bit indicates that the gain is set on the corresponding channel-maps. + Bit position of the channel-map is obtained by left shifting (1 (left shift) (Channel_map - 32)) + } + @h2xmle_default {0xFFFFFFFF} */ + + uint32_t gain; + /**< @h2xmle_description {Gain value for the above channels in Q28 format} + @h2xmle_dataFormat {Q28} + @h2xmle_default {0x10000000} */ +} +#include "spf_end_pack.h" +; + +/* Structure type def for above payload. */ +typedef struct volume_ctrl_channels_gain_config_t volume_ctrl_channels_gain_config_t; + + +/* ID of the Multi-channel Volume Control parameters used by #MODULE_ID_VOL_CTRL. */ +#define PARAM_ID_VOL_CTRL_MULTICHANNEL_GAIN 0x08001038 +/** @h2xmlp_parameter {"PARAM_ID_VOL_CTRL_MULTICHANNEL_GAIN",PARAM_ID_VOL_CTRL_MULTICHANNEL_GAIN} + @h2xmlp_description { Payload of the multi channel type gain pairs used by the Volume Control module} + @h2xmlp_toolPolicy {NO_SUPPORT} + @h2xmlp_maxSize {760}*/ + +/* Structure for the multichannel gain command */ +#include "spf_begin_pack.h" +#include "spf_begin_pragma.h" +struct volume_ctrl_multichannel_gain_t +{ + uint32_t num_config; + /**< @h2xmle_description {Number of channels-gain configurations provided} + @h2xmle_range {1..63} + @h2xmle_default {1} */ + + volume_ctrl_channels_gain_config_t gain_data[0]; + /**< @h2xmle_description {Payload consisting of all channels-gain pairs } + @h2xmle_variableArraySize {num_config} */ + +} +#include "spf_end_pragma.h" +#include "spf_end_pack.h" +; + +/* Structure type def for above payload. */ +typedef struct volume_ctrl_multichannel_gain_t volume_ctrl_multichannel_gain_t; + +/* @h2xml_Select {volume_ctrl_channels_gain_config_t} + @h2xmlm_InsertParameter */ + + + +/* Structure for holding one channel type - mute pair. */ +/** @h2xmlp_subStruct */ +#include "spf_begin_pack.h" +struct volume_ctrl_channels_mute_config_t +{ + uint32_t channel_mask_lsb; + /**< @h2xmle_description {Lower 32 bits of the channel mask. Each bit corresponds to channel map + from 1 (PCM_CHANNEL_L) to 31 (PCM_CHANNEL_LW). + Bit 0 is reserved and must be set to zero.\n + -#A set bit indicates whether mute is enabled on the corresponding channel-maps. + Bit position of the channel-map is obtained by left shifting (1 (left shift) Channel_map) + } + @h2xmle_default {0xFFFFFFFE} */ + + + uint32_t channel_mask_msb; + /**< @h2xmle_description {Upper 32 bits of the channel mask. Each bit corresponds to channel map + from 32 (PCM_CHANNEL_RW) to 63 (PCM_CUSTOM_CHANNEL_MAP_16).\n + -#A set bit indicates whether mute is enabled on the corresponding channel-maps. + Bit position of the channel-map is obtained by left shifting (1 (left shift) (Channel_map - 32)) + } + @h2xmle_default {0xFFFFFFFF} */ + + uint32_t mute; + /**< @h2xmle_description { Specifies mute for the above channels } + @h2xmle_rangeList {"Disable"= 0; + "Enable"=1} + @h2xmle_default {0} + */ +} +#include "spf_end_pack.h" +; + +/* Structure type def for above payload. */ +typedef struct volume_ctrl_channels_mute_config_t volume_ctrl_channels_mute_config_t; + +/* ID of the Multichannel Mute Configuration parameters used by #MODULE_ID_VOL_CTRL. */ +#define PARAM_ID_VOL_CTRL_MULTICHANNEL_MUTE 0x08001039 +/** @h2xmlp_parameter {"PARAM_ID_VOL_CTRL_MULTICHANNEL_MUTE", PARAM_ID_VOL_CTRL_MULTICHANNEL_MUTE} + @h2xmlp_description {Payload of the PARAM_ID_VOL_CTRL_MULTICHANNEL_MUTE channel type/mute + setting pairs used by the Volume Control module} + @h2xmlp_toolPolicy {NO_SUPPORT} + @h2xmlp_maxSize {760} */ + +/* Structure for the multichannel mute command */ +#include "spf_begin_pack.h" +#include "spf_begin_pragma.h" +struct volume_ctrl_multichannel_mute_t +{ + uint32_t num_config; + /**< @h2xmle_description {Number of channels-mute configurations provided} + @h2xmle_range {1..63} + @h2xmle_default {1} */ + + volume_ctrl_channels_mute_config_t mute_data[0]; + /**< @h2xmle_description {Array of channels-mute setting pairs} + @h2xmle_variableArraySize {num_config}*/ +} +#include "spf_end_pragma.h" +#include "spf_end_pack.h" +; + +/* Structure type def for above payload. */ +typedef struct volume_ctrl_multichannel_mute_t volume_ctrl_multichannel_mute_t; + +/* @h2xml_Select {volume_ctrl_channels_mute_config_t} + @h2xmlm_InsertParameter */ + + +/* Structure for holding one channel type - gain pair. */ +/** @h2xmlp_subStruct */ +#include "spf_begin_pack.h" +#include "spf_begin_pragma.h" +struct volume_ctrl_channels_gain_config_v2_t +{ + uint32_t channel_type_group_mask; + /**< @h2xmle_description {Indicates the mask for channel_type_mask_list array. + Each bit in channel_type_group_mask corresponds to a channel group. + Read as + Bit 0 corresponds to channel group 1, which includes channel map for channels 1-31. + Bit 1 corresponds to channel group 2, which includes channel map for channels 32-63. + Bit 2 corresponds to channel group 3, which includes channel map for channels 64-95. + Bit 3 corresponds to channel group 4, which includes channel map for channels 96-127. + Bit 4 corresponds to channel group 5, which includes channel map for channels 128-159. + A set bit (1) in channel_type_group_mask indicates that the channels in that channel group are configured. + } + @h2xmle_range {0..31} + @h2xmle_default {0x00000003} */ + + + uint32_t channel_type_mask_list[0]; + /**< @h2xmle_description {An array used to configure the channels for different channel groups. The array size depends on the number of + bits set in channel_type_group_mask.\n + For group 1, each bit of channel_type_mask_list corresponds to channel map from 1 (PCM_CHANNEL_L) to 31 (PCM_CHANNEL_LW).\n + Bit 0 of group 1 channel_type_mask_list is reserved and must always be set to zero.\n + For any other group, each bit of channel_type_mask_list corresponds to channel map from [32(group_no -1) to 32(group_no)-1].\n + Bit position of the channel-map for channel_type_mask_list of defined group is obtained by left shifting (1 (left shift) Channel_map%32 + } + @h2xmle_variableArraySizeFunction {GET_SET_BITS_COUNT, channel_type_group_mask} + @h2xmle_copySrcList {channel_mask_lsb, channel_mask_msb} + @h2xmle_defaultList {0xfffffffe, 0xffffffff} */ + + uint32_t gain; + /**< @h2xmle_description {Gain value for the above channels in Q28 format} + @h2xmle_copySrc {gain} + @h2xmle_dataFormat {Q28} + @h2xmle_default {0x10000000} */ +} +#include "spf_end_pragma.h" +#include "spf_end_pack.h" +; + +/* Structure type def for above payload. */ +typedef struct volume_ctrl_channels_gain_config_v2_t volume_ctrl_channels_gain_config_v2_t; + + +/* ID of the Multi-channel Volume Control parameters used by #MODULE_ID_VOL_CTRL. */ +#define PARAM_ID_VOL_CTRL_MULTICHANNEL_GAIN_V2 0x8001A88 +/** @h2xmlp_parameter {"PARAM_ID_VOL_CTRL_MULTICHANNEL_GAIN_V2",PARAM_ID_VOL_CTRL_MULTICHANNEL_GAIN_V2} + @h2xmlp_copySrc {0x08001038} + @h2xmlp_description { Payload of the multi channel type gain pairs used by the Volume Control module} + @h2xmlp_toolPolicy {Calibration; RTC}*/ + +/* Structure for the multichannel gain command */ +#include "spf_begin_pack.h" +#include "spf_begin_pragma.h" +struct volume_ctrl_multichannel_gain_v2_t +{ + uint32_t num_config; + /**< @h2xmle_description {Number of channels-gain configurations provided} + @h2xmle_copySrc {num_config} + @h2xmle_range {1..MODULE_CMN_MAX_CHANNEL} + @h2xmle_default {1} */ + + volume_ctrl_channels_gain_config_v2_t gain_config[0]; + /**< @h2xmle_description {Payload consisting of all channels-gain pairs } + @h2xmle_variableArraySize {num_config} */ + +} +#include "spf_end_pragma.h" +#include "spf_end_pack.h" +; + +/* Structure type def for above payload. */ +typedef struct volume_ctrl_multichannel_gain_v2_t volume_ctrl_multichannel_gain_v2_t; + +/* @h2xml_Select {volume_ctrl_multichannel_gain_v2_t} + @h2xmlm_InsertParameter */ + + + +/* Structure for holding one channel type - mute pair. */ +/** @h2xmlp_subStruct */ +#include "spf_begin_pack.h" +#include "spf_begin_pragma.h" +struct volume_ctrl_channels_mute_config_v2_t +{ + uint32_t channel_type_group_mask; + /**< @h2xmle_description {Indicates the mask for channel_type_mask_list array. + Each bit in channel_type_group_mask corresponds to a channel group. + Read as + Bit 0 corresponds to channel group 1, which includes channel map for channels 1-31. + Bit 1 corresponds to channel group 2, which includes channel map for channels 32-63. + Bit 2 corresponds to channel group 3, which includes channel map for channels 64-95. + Bit 3 corresponds to channel group 4, which includes channel map for channels 96-127. + Bit 4 corresponds to channel group 5, which includes channel map for channels 128-159. + + A set bit (1) in channel_type_group_mask indicates that the channels in that channel group are configured. + } + @h2xmle_range {0..31} + @h2xmle_default {0x00000003} */ + + + uint32_t channel_type_mask_list[0]; + /**< @h2xmle_description {An array used to configure the channels for different channel groups. The array size depends on the number of + bits set in channel_type_group_mask.\n + For group 1, each bit of channel_type_mask_list corresponds to channel map from 1 (PCM_CHANNEL_L) to 31 (PCM_CHANNEL_LW).\n + Bit 0 of group 1 channel_type_mask_list is reserved and must always be set to zero.\n + For any other group, each bit of channel_type_mask_list corresponds to channel map from [32(group_no -1) to 32(group_no)-1].\n + Bit position of the channel-map for channel_type_mask_list of defined group is obtained by left shifting (1 (left shift) Channel_map%32 + } + @h2xmle_variableArraySizeFunction {GET_SET_BITS_COUNT, channel_type_group_mask} + @h2xmle_copySrcList {channel_mask_lsb, channel_mask_msb} + @h2xmle_defaultList {0xfffffffe, 0xffffffff} */ + + uint32_t mute; + /**< @h2xmle_description { Specifies mute for the above channels } + @h2xmle_copySrc {mute} + @h2xmle_rangeList {"Disable"= 0; + "Enable"=1} + @h2xmle_default {0} + */ +} +#include "spf_end_pragma.h" +#include "spf_end_pack.h" +; + +/* Structure type def for above payload. */ +typedef struct volume_ctrl_channels_mute_config_v2_t volume_ctrl_channels_mute_config_v2_t; + +/* ID of the Multichannel Mute Configuration parameters used by #MODULE_ID_VOL_CTRL. */ +#define PARAM_ID_VOL_CTRL_MULTICHANNEL_MUTE_V2 0x8001A89 +/** @h2xmlp_parameter {"PARAM_ID_VOL_CTRL_MULTICHANNEL_MUTE_V2", PARAM_ID_VOL_CTRL_MULTICHANNEL_MUTE_V2} + @h2xmlp_copySrc {0x08001039} + @h2xmlp_description {Payload of the PARAM_ID_VOL_CTRL_MULTICHANNEL_MUTE_V2 channel type/mute + setting pairs used by the Volume Control module} + @h2xmlp_toolPolicy {Calibration; RTC} */ + +/* Structure for the multichannel mute command */ +#include "spf_begin_pack.h" +#include "spf_begin_pragma.h" +struct volume_ctrl_multichannel_mute_v2_t +{ + uint32_t num_config; + /**< @h2xmle_description {Number of channels-mute configurations provided} + @h2xmle_copySrc {num_config} + @h2xmle_range {1..MODULE_CMN_MAX_CHANNEL} + @h2xmle_default {1} */ + + volume_ctrl_channels_mute_config_v2_t mute_config[0]; + /**< @h2xmle_description {Array of channels-mute setting pairs} + @h2xmle_variableArraySize {num_config}*/ +} +#include "spf_end_pragma.h" +#include "spf_end_pack.h" +; + +/* Structure type def for above payload. */ +typedef struct volume_ctrl_multichannel_mute_v2_t volume_ctrl_multichannel_mute_v2_t; + +/* @h2xml_Select {volume_ctrl_channels_mute_config_v2_t} + @h2xmlm_InsertParameter */ + + + +/** @} <-- End of the Module -->*/ +#endif //_SOFT_VOL_API_H_ diff --git a/modules/processing/volume_control/capi/soft_vol/build/CMakeLists.txt b/modules/processing/volume_control/capi/soft_vol/build/CMakeLists.txt new file mode 100644 index 0000000..9a271d0 --- /dev/null +++ b/modules/processing/volume_control/capi/soft_vol/build/CMakeLists.txt @@ -0,0 +1,44 @@ +#[[ + @file CMakeLists.txt + + @brief + + @copyright + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause-Clear +]] +cmake_minimum_required(VERSION 3.10) + +set(soft_vol_sources + ${LIB_ROOT}/src/capi_soft_vol.cpp + ${LIB_ROOT}/src/capi_soft_vol_island.cpp + ${LIB_ROOT}/src/capi_soft_vol_utils.cpp + ${LIB_ROOT}/src/capi_soft_vol_utils_v2.cpp + ../../../lib/src/softvolumecontrols.cpp + ../../../lib/src/softvolumecontrols_island.cpp +) + +set(soft_vol_includes + ${LIB_ROOT}/api + ${LIB_ROOT}/inc + ${LIB_ROOT}/src + ../../../lib/inc + ../../../../../cmn/common/internal_api + ../../../../../cmn/common/utils/inc +) + +spf_module_sources( + KCONFIG CONFIG_SOFT_VOL + NAME soft_vol + MAJOR_VER 1 + MINOR_VER 0 + AMDB_ITYPE "capi" + AMDB_MTYPE "PP" + AMDB_MID "0x0700101B" + AMDB_TAG "capi_soft_vol" + AMDB_MOD_NAME "MODULE_ID_VOL_CTRL" + SRCS ${soft_vol_sources} + INCLUDES ${soft_vol_includes} + H2XML_HEADERS "${LIB_ROOT}/api/soft_vol_api.h" + CFLAGS "" +) diff --git a/modules/processing/volume_control/capi/soft_vol/inc/capi_soft_vol.h b/modules/processing/volume_control/capi/soft_vol/inc/capi_soft_vol.h new file mode 100644 index 0000000..2d30dec --- /dev/null +++ b/modules/processing/volume_control/capi/soft_vol/inc/capi_soft_vol.h @@ -0,0 +1,40 @@ +/* ========================================================================= + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause-Clear + * =========================================================================*/ + +/** + * @file capi_soft_vol.h + * + * Common Audio Processor Interface for soft_vol. + */ + +#ifndef CAPI_SOFT_VOL_H +#define CAPI_SOFT_VOL_H + +#include "capi.h" +#include "ar_defs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Get static properties of soft_vol module such as + * memory, stack requirements etc. + * See Elite_CAPI.h for more details. + */ +capi_err_t capi_soft_vol_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_soft_vol_init(capi_t *_pif, capi_proplist_t *init_set_properties); + +#ifdef __cplusplus +} +#endif + +#endif /* CAPI_SOFT_VOL_H */ diff --git a/modules/processing/volume_control/capi/soft_vol/src/capi_soft_vol.cpp b/modules/processing/volume_control/capi/soft_vol/src/capi_soft_vol.cpp new file mode 100644 index 0000000..2ecae25 --- /dev/null +++ b/modules/processing/volume_control/capi/soft_vol/src/capi_soft_vol.cpp @@ -0,0 +1,670 @@ +/* ========================================================================= + * Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + * SPDX-License-Identifier: BSD-3-Clause-Clear + * =========================================================================*/ + +/** + * @file capi_soft_vol.cpp + * + * Implementation for soft_vol module + */ + +#include "capi_soft_vol.h" +#include "capi_soft_vol_utils.h" + +//#define SOFT_VOL_DEBUG 1 + +#ifdef DO_SOFT_VOL_PROFILING +#include +#endif + +capi_err_t capi_soft_vol_get_static_properties(capi_proplist_t *init_set_properties, + capi_proplist_t *static_properties) +{ + return capi_soft_vol_process_get_properties((capi_soft_vol_t *)NULL, static_properties); +} + +capi_err_t capi_soft_vol_init(capi_t *_pif, capi_proplist_t *init_set_properties) +{ + capi_err_t result = CAPI_EOK; + + if (NULL == _pif) + return CAPI_EBADPARAM; + + capi_soft_vol_t *me_ptr = (capi_soft_vol_t *)_pif; + memset(me_ptr, 0, sizeof(capi_soft_vol_t)); + me_ptr->soft_vol_state = SOFT_VOL_WAITING_FOR_MEDIA_FORMAT; + me_ptr->vtbl_ptr = capi_soft_vol_get_vtbl(); // assigning the vtbl with all function pointers + + result = capi_cmn_init_media_fmt_v2(&me_ptr->input_media_fmt); + + capi_soft_vol_init_lib_memory(me_ptr); + + /* init_set_properties contains OUT_BITS_PER_SAMPLE, + * EVENT_CALLBACK_INFO and PORT_INFO */ + result = capi_soft_vol_process_set_properties(me_ptr, init_set_properties); + + // Initialize the control port list. + capi_cmn_ctrl_port_list_init(&me_ptr->ctrl_port_info); + + return result; +} + +capi_err_t capi_soft_vol_end(capi_t *_pif) +{ + if (NULL == _pif) + { + SOFT_VOL_MSG(MIID_UNKNOWN, DBG_ERROR_PRIO, "End received bad pointer, 0x%p", _pif); + return CAPI_EBADPARAM; + } + + capi_soft_vol_t *me_ptr = (capi_soft_vol_t *)(_pif); + +#ifdef DO_SOFT_VOL_PROFILING + capi_soft_vol_print_kpps(me_ptr); +#endif + + CSoftVolumeControlsLib *pLib = &me_ptr->SoftVolumeControlsLib; + posal_memory_placement_delete(pLib, CSoftVolumeControlsLib); + capi_cmn_ctrl_port_list_deinit(&me_ptr->ctrl_port_info); + me_ptr->vtbl_ptr = NULL; + + return CAPI_EOK; +} + +capi_err_t capi_soft_vol_set_param(capi_t * _pif, + uint32_t param_id, + const capi_port_info_t *port_info_ptr, + capi_buf_t * params_ptr) +{ + if (NULL == _pif || NULL == params_ptr) + { + SOFT_VOL_MSG(MIID_UNKNOWN, DBG_ERROR_PRIO, "Set param received bad pointers 0x%p, 0x%p", _pif, params_ptr); + return CAPI_EBADPARAM; + } + capi_err_t result = CAPI_EOK; + capi_soft_vol_t *me_ptr = (capi_soft_vol_t *)(_pif); + me_ptr->adjust_volume_based_on_headroom = 0; + me_ptr->update_gain_over_imcl = TRUE; + + switch (param_id) + { + case PARAM_ID_VOL_CTRL_MASTER_GAIN: + { + if (params_ptr->actual_data_len >= sizeof(volume_ctrl_master_gain_t)) + { + volume_ctrl_master_gain_t *pMasterGainPacket = (volume_ctrl_master_gain_t *)(params_ptr->data_ptr); + me_ptr->soft_vol_lib.masterGain = pMasterGainPacket->master_gain; + for (uint32_t i = MIN_CHANNEL_TYPE; i <= MAX_CHANNEL_TYPE; i++) + { + uint32 gainQ28 = + capi_soft_vol_calc_gain_q28(me_ptr->soft_vol_lib.masterGain, me_ptr->soft_vol_lib.channelGain[i]); + me_ptr->SoftVolumeControlsLib.SetVolume(gainQ28, me_ptr->soft_vol_lib.pPerChannelData[i]); + } + result = CAPI_EOK; + + SOFT_VOL_MSG(me_ptr->miid, DBG_HIGH_PRIO, + "Set param PARAM_ID_VOL_CTRL_MASTER_GAIN, 0x%x", + pMasterGainPacket->master_gain); + } + else + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, + "Set Master Gain, Bad param size %lu", + params_ptr->actual_data_len); + result = CAPI_ENEEDMORE; + } + break; + } + case PARAM_ID_VOL_CTRL_MASTER_MUTE: + { + if (params_ptr->actual_data_len >= sizeof(volume_ctrl_master_mute_t)) + + { + volume_ctrl_master_mute_t *pMutePacket = (volume_ctrl_master_mute_t *)((params_ptr->data_ptr)); + capi_soft_vol_set_mute_for_all_channels(me_ptr, pMutePacket->mute_flag); + + result = CAPI_EOK; + + SOFT_VOL_MSG(me_ptr->miid, DBG_HIGH_PRIO, + "Set param PARAM_ID_VOL_CTRL_MASTER_MUTE, %lu", + pMutePacket->mute_flag); + } + else + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, "Set Mute, Bad param size %lu", params_ptr->actual_data_len); + result = CAPI_ENEEDMORE; + } + break; + } + case PARAM_ID_VOL_CTRL_GAIN_RAMP_PARAMETERS: + { + if (params_ptr->actual_data_len >= sizeof(volume_ctrl_gain_ramp_params_t)) + { + volume_ctrl_gain_ramp_params_t *p_soft_stepping_pkt = + (volume_ctrl_gain_ramp_params_t *)(params_ptr->data_ptr); + SoftSteppingParams params; + capi_set_soft_stepping_param(me_ptr, + ¶ms, + p_soft_stepping_pkt->period_ms, + p_soft_stepping_pkt->step_us, + p_soft_stepping_pkt->ramping_curve); + me_ptr->SoftVolumeControlsLib.SetSoftVolumeParams(params); + + SOFT_VOL_MSG(me_ptr->miid, DBG_HIGH_PRIO, + "Set PARAM_ID_VOL_CTRL_GAIN_RAMP_PARAMETERS: period %d, step %d, curve %d ", + p_soft_stepping_pkt->period_ms, + p_soft_stepping_pkt->step_us, + p_soft_stepping_pkt->ramping_curve); + } + else + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, + "Set gain ramp params, Bad param size %lu", + params_ptr->actual_data_len); + result = CAPI_ENEEDMORE; + } + break; + } + case PARAM_ID_VOL_CTRL_MUTE_RAMP_PARAMETERS: + { + if (params_ptr->actual_data_len >= sizeof(volume_ctrl_mute_ramp_params_t)) + { + volume_ctrl_mute_ramp_params_t *p_soft_stepping_pkt = + (volume_ctrl_mute_ramp_params_t *)(params_ptr->data_ptr); + SoftSteppingParams params; + capi_set_soft_stepping_param(me_ptr, + ¶ms, + p_soft_stepping_pkt->period_ms, + p_soft_stepping_pkt->step_us, + p_soft_stepping_pkt->ramping_curve); + me_ptr->SoftVolumeControlsLib.SetSoftMuteParams(params); + + SOFT_VOL_MSG(me_ptr->miid, DBG_HIGH_PRIO, + "Set PARAM_ID_VOL_CTRL_MUTE_RAMP_PARAMETERS: period %d, step %d, curve %d ", + p_soft_stepping_pkt->period_ms, + p_soft_stepping_pkt->step_us, + p_soft_stepping_pkt->ramping_curve); + } + else + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, + "Set mute ramp params, Bad param size %lu", + params_ptr->actual_data_len); + result = CAPI_ENEEDMORE; + } + break; + } + case PARAM_ID_VOL_CTRL_MULTICHANNEL_GAIN: + { + if (params_ptr->actual_data_len >= sizeof(volume_ctrl_multichannel_gain_t)) + { + volume_ctrl_multichannel_gain_t *pVolumePkt = (volume_ctrl_multichannel_gain_t *)(params_ptr->data_ptr); + + uint32_t param_total_size = + (pVolumePkt->num_config * sizeof(volume_ctrl_channels_gain_config_t)) + sizeof(uint32_t); + if (params_ptr->actual_data_len < param_total_size) + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, "Insufficient payload size %d", params_ptr->actual_data_len); + result = CAPI_ENEEDMORE; + } + else + { + SOFT_VOL_MSG(me_ptr->miid, DBG_HIGH_PRIO, "Set PARAM_ID_VOL_CTRL_MULTICHANNEL_GAIN "); + result = capi_soft_vol_set_multichannel_gain(me_ptr, pVolumePkt); + } + } + else + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, + "Set multichannel volume, Bad param size %lu", + params_ptr->actual_data_len); + result = CAPI_ENEEDMORE; + } + break; + } + case PARAM_ID_VOL_CTRL_MULTICHANNEL_GAIN_V2: + { + if (params_ptr->actual_data_len >= sizeof(volume_ctrl_multichannel_gain_v2_t)) + { + volume_ctrl_multichannel_gain_v2_t *pVolumePkt = ((volume_ctrl_multichannel_gain_v2_t *)params_ptr->data_ptr); + int8_t *pVolumePayload = params_ptr->data_ptr; + + // validate received num of cfg + const uint32_t num_cfg = pVolumePkt->num_config; + if (num_cfg < 1 || num_cfg > PCM_MAX_CHANNEL_MAP_V2) + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, "Received incorrect num_config parameter - %lu", num_cfg); + return CAPI_EBADPARAM; + } + // validate received payload + uint32_t required_size = 0; + result = capi_soft_vol_validate_multichannel_payload(me_ptr, num_cfg, pVolumePayload, params_ptr->actual_data_len, + &required_size, param_id); + if (CAPI_FAILED(result)) + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, "Multichannel gain SetParam 0x%lx, invalid param size %lu ,required_size %lu", + param_id, + params_ptr->actual_data_len, + required_size); + return result; + } + if (params_ptr->actual_data_len < required_size) + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, "Insufficient payload size %d. Req size %lu", params_ptr->actual_data_len, required_size); + result = CAPI_ENEEDMORE; + } + else + { + SOFT_VOL_MSG(me_ptr->miid, DBG_HIGH_PRIO, "Set PARAM_ID_VOL_CTRL_MULTICHANNEL_GAIN_V2 "); + result = capi_soft_vol_set_multichannel_gain_v2(me_ptr, pVolumePkt); + } + } + else + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, + "Set multichannel volume, Bad param size %lu", + params_ptr->actual_data_len); + result = CAPI_ENEEDMORE; + } + break; + } + + case PARAM_ID_VOL_CTRL_MULTICHANNEL_MUTE: + { + if (params_ptr->actual_data_len >= sizeof(volume_ctrl_multichannel_mute_t)) + { + volume_ctrl_multichannel_mute_t *pMutePkt = (volume_ctrl_multichannel_mute_t *)(params_ptr->data_ptr); + uint32_t param_total_size = + (pMutePkt->num_config * sizeof(volume_ctrl_channels_mute_config_t)) + sizeof(uint32_t); + if (params_ptr->actual_data_len < param_total_size) + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, "Insufficient payload size %d", params_ptr->actual_data_len); + result = CAPI_ENEEDMORE; + } + else + { + SOFT_VOL_MSG(me_ptr->miid, DBG_HIGH_PRIO, "Set PARAM_ID_VOL_CTRL_MULTICHANNEL_MUTE "); + result = capi_soft_vol_set_multichannel_mute(me_ptr, pMutePkt); + } + } + else + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, + "Set multichannel mute, Bad param size %lu", + params_ptr->actual_data_len); + result = CAPI_ENEEDMORE; + } + break; + } + case PARAM_ID_VOL_CTRL_MULTICHANNEL_MUTE_V2: + { + if (params_ptr->actual_data_len >= sizeof(volume_ctrl_multichannel_mute_v2_t)) + { + volume_ctrl_multichannel_mute_v2_t *pMutePkt = ((volume_ctrl_multichannel_mute_v2_t *)params_ptr->data_ptr); + int8_t *pMutePayload = params_ptr->data_ptr; + // validate received num of cfg + const uint32_t num_cfg = pMutePkt->num_config; + if (num_cfg < 1 || num_cfg > PCM_MAX_CHANNEL_MAP_V2) + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, "Received incorrect num_config parameter - %lu", num_cfg); + return CAPI_EBADPARAM; + } + // validate received payload size + uint32_t required_size = 0; + result = capi_soft_vol_validate_multichannel_payload(me_ptr, num_cfg, pMutePayload, params_ptr->actual_data_len, + &required_size, param_id); + if (CAPI_FAILED(result)) + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, "Multichannel mute SetParam 0x%lx, invalid param size %lu ,required_size %lu", + param_id, + params_ptr->actual_data_len, + required_size); + return result; + } + else + { + SOFT_VOL_MSG(me_ptr->miid, DBG_HIGH_PRIO, "Set PARAM_ID_VOL_CTRL_MULTICHANNEL_MUTE_V2 "); + result = capi_soft_vol_set_multichannel_mute_v2(me_ptr, pMutePkt); + } + } + else + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, + "Set multichannel mute, Bad param size %lu", + params_ptr->actual_data_len); + result = CAPI_ENEEDMORE; + } + break; + } + + case PARAM_ID_ALGORITHMIC_HEADROOM: + { + if (params_ptr->actual_data_len >= sizeof(int32_t)) + { + int32_t headroom_mB = int32_t(*((int32_t *)(params_ptr->data_ptr))); + if (me_ptr->soft_vol_lib.headroom_mB != headroom_mB) + { + me_ptr->soft_vol_lib.headroom_mB = headroom_mB; + } + else + { + me_ptr->adjust_volume_based_on_headroom = 0; + } + } + else + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, "Headroom, Bad param size %lu", params_ptr->actual_data_len); + result = CAPI_ENEEDMORE; + } + break; + } + case INTF_EXTN_PARAM_ID_IMCL_PORT_OPERATION: + { + uint32_t supported_intent[3] = { INTENT_ID_GAIN_INFO, INTENT_ID_P_EQ_VOL_HEADROOM, INTENT_ID_MUTE }; + result = capi_cmn_ctrl_port_operation_handler(&me_ptr->ctrl_port_info, + params_ptr, + (POSAL_HEAP_ID)me_ptr->heap_id, + 0, + 3, + supported_intent); + break; + } + case INTF_EXTN_PARAM_ID_IMCL_INCOMING_DATA: + { + result = capi_vol_imc_set_param_handler(me_ptr, params_ptr); + if(CAPI_EOK != result) + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, "IMC set param handler failed 0x%x \n", param_id); + } + break; + } + default: + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, "Set, unsupported param ID %#x", param_id); + result = CAPI_EUNSUPPORTED; + break; + } + } /* switch (param_id) */ + + if (me_ptr->adjust_volume_based_on_headroom) + result |= capi_soft_vol_headroom_gain(me_ptr, me_ptr->soft_vol_lib.headroom_mB); + + if (SOFT_VOL_ENABLE == me_ptr->soft_vol_state) + { + uint32_t process_check = 0; + for (uint32_t i = 0; i < me_ptr->soft_vol_lib.numChannels; i++) + { + if (!me_ptr->SoftVolumeControlsLib.isUnityGain( + me_ptr->soft_vol_lib.pPerChannelData[me_ptr->soft_vol_lib.channelMapping[i]])) + { + process_check = 1; + break; + } + } + result |= capi_cmn_update_process_check_event(&me_ptr->cb_info, process_check); + } + return result; +} + +capi_err_t capi_soft_vol_get_param(capi_t * _pif, + uint32_t param_id, + const capi_port_info_t *port_info_ptr, + capi_buf_t * params_ptr) + +{ + if (NULL == _pif || NULL == params_ptr) + { + SOFT_VOL_MSG(MIID_UNKNOWN, DBG_ERROR_PRIO, + "Get param received bad pointer," + " 0x%p, 0x%p", + _pif, + params_ptr); + return CAPI_EBADPARAM; + } + + capi_err_t result = CAPI_EOK; + capi_soft_vol_t *me_ptr = (capi_soft_vol_t *)(_pif); + + if ((TRUE == me_ptr->higher_channel_map_present) && ((PARAM_ID_VOL_CTRL_MULTICHANNEL_GAIN == param_id) || (PARAM_ID_VOL_CTRL_MULTICHANNEL_MUTE == param_id))) + { + SOFT_VOL_MSG(me_ptr->miid,DBG_ERROR_PRIO, "GetParam 0x%x failed as higher than 63 channel map present in IMF (0(No)/1(Yes)): %lu." + "V1 API is not sufficient to have higher channel maps.", + (int)param_id, + me_ptr->higher_channel_map_present); + return CAPI_EBADPARAM; + } + switch (param_id) + { + case PARAM_ID_VOL_CTRL_MASTER_GAIN: + { + if (params_ptr->max_data_len >= sizeof(volume_ctrl_master_gain_t)) + { + volume_ctrl_master_gain_t *pMasterGainPacket = (volume_ctrl_master_gain_t *)(params_ptr->data_ptr); + + pMasterGainPacket->master_gain = me_ptr->soft_vol_lib.masterGain; + pMasterGainPacket->reserved = 0; + params_ptr->actual_data_len = sizeof(volume_ctrl_master_gain_t); +#ifdef CAPI_SOFT_VOLUME_DEBUG_MSG + SOFT_VOL_MSG(me_ptr->miid, DBG_HIGH_PRIO, "Get Master Gain, %d", pMasterGainPacket->master_gain); +#endif + } + else + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, "Get Master Gain, Bad param size %lu", params_ptr->max_data_len); + result = CAPI_ENEEDMORE; + } + break; + } + case PARAM_ID_VOL_CTRL_MASTER_MUTE: + { + if (params_ptr->max_data_len >= sizeof(volume_ctrl_master_mute_t)) + { + volume_ctrl_master_mute_t *pMutePacket = (volume_ctrl_master_mute_t *)(params_ptr->data_ptr); + pMutePacket->mute_flag = 1; + for (uint32_t i = 0; i < me_ptr->soft_vol_lib.numChannels; i++) + { + if (!me_ptr->SoftVolumeControlsLib.IsMuted( + me_ptr->soft_vol_lib.pPerChannelData[me_ptr->soft_vol_lib.channelMapping[i]])) + { + pMutePacket->mute_flag = 0; + break; + } + } + params_ptr->actual_data_len = sizeof(volume_ctrl_master_mute_t); + result = AR_EOK; +#ifdef CAPI_SOFT_VOLUME_DEBUG_MSG + SOFT_VOL_MSG(me_ptr->miid, DBG_HIGH_PRIO, "Get Mute, %lu", pMutePacket->mute_flag); +#endif + } + else + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, "Get Mute, Bad param size %lu", params_ptr->max_data_len); + result = CAPI_ENEEDMORE; + } + break; + } + case PARAM_ID_VOL_CTRL_GAIN_RAMP_PARAMETERS: + { + if (params_ptr->max_data_len >= sizeof(volume_ctrl_gain_ramp_params_t)) + { + volume_ctrl_gain_ramp_params_t *p_soft_stepping_pkt = + (volume_ctrl_gain_ramp_params_t *)(params_ptr->data_ptr); + SoftSteppingParams params; + me_ptr->SoftVolumeControlsLib.GetSoftVolumeParams(¶ms); + capi_get_soft_stepping_param(me_ptr, + ¶ms, + &p_soft_stepping_pkt->period_ms, + &p_soft_stepping_pkt->step_us, + &p_soft_stepping_pkt->ramping_curve); + params_ptr->actual_data_len = sizeof(volume_ctrl_gain_ramp_params_t); + } + else + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, + "Get gain ramp params, Bad param size %lu", + params_ptr->max_data_len); + result = CAPI_ENEEDMORE; + } + break; + } + case PARAM_ID_VOL_CTRL_MUTE_RAMP_PARAMETERS: + { + if (params_ptr->max_data_len >= sizeof(volume_ctrl_mute_ramp_params_t)) + { + volume_ctrl_mute_ramp_params_t *p_soft_stepping_pkt = + (volume_ctrl_mute_ramp_params_t *)(params_ptr->data_ptr); + SoftSteppingParams params; + me_ptr->SoftVolumeControlsLib.GetSoftMuteParams(¶ms); + capi_get_soft_stepping_param(me_ptr, + ¶ms, + &p_soft_stepping_pkt->period_ms, + &p_soft_stepping_pkt->step_us, + &p_soft_stepping_pkt->ramping_curve); + params_ptr->actual_data_len = sizeof(volume_ctrl_mute_ramp_params_t); + } + else + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, + "Get Mute ramp params, Bad param size %lu", + params_ptr->max_data_len); + result = CAPI_ENEEDMORE; + } + break; + } + case PARAM_ID_VOL_CTRL_MULTICHANNEL_GAIN: + { + if(me_ptr->higher_channel_map_present) + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO,"Get Multichannel Gain, not supported with V1 param, as higher channel maps are present in input media format."); + return CAPI_EBADPARAM; + } + uint32_t num_configs = capi_soft_vol_get_multichannel_gain(me_ptr, NULL); + uint32_t req_size = + sizeof(volume_ctrl_multichannel_gain_t) + (num_configs * sizeof(volume_ctrl_channels_gain_config_t)); + + if (params_ptr->max_data_len >= req_size) + { + volume_ctrl_multichannel_gain_t *multi_gain_ptr = (volume_ctrl_multichannel_gain_t *)(params_ptr->data_ptr); + capi_soft_vol_get_multichannel_gain(me_ptr, multi_gain_ptr); + } + else + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, + "multichannel volume, Bad param size %lu", + params_ptr->max_data_len); + result = CAPI_ENEEDMORE; + } + params_ptr->actual_data_len = req_size; + break; + } + case PARAM_ID_VOL_CTRL_MULTICHANNEL_MUTE: + { + if(me_ptr->higher_channel_map_present) + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO,"Get Multichannel Mute, not supported with V1 param, as higher channel maps are present in input media format."); + return CAPI_EBADPARAM; + } + //num configs is 1 + uint32_t req_size = params_ptr->actual_data_len = + sizeof(volume_ctrl_multichannel_mute_t) + (sizeof(volume_ctrl_channels_mute_config_t)); + + if (params_ptr->max_data_len >= sizeof(volume_ctrl_multichannel_mute_t)) + { + volume_ctrl_multichannel_mute_t *multi_mute_ptr = (volume_ctrl_multichannel_mute_t *)(params_ptr->data_ptr); + capi_soft_vol_get_multichannel_mute(me_ptr, multi_mute_ptr); + } + else + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, + "Get Multichannel Mute, Bad param size %lu", + params_ptr->max_data_len); + result = CAPI_ENEEDMORE; + } + params_ptr->actual_data_len = req_size; + break; + } + case PARAM_ID_VOL_CTRL_MULTICHANNEL_GAIN_V2: + { + capi_soft_vol_multich_gain_info gain_list[VOLUME_CONTROL_MAX_CHANNELS] = { { 0 } }; // list to maintain all unique gains + uint32_t req_size = 0; + uint32_t num_unique_gains = 0; + + if (params_ptr->max_data_len >= sizeof(volume_ctrl_multichannel_gain_v2_t)) + { + num_unique_gains = capi_soft_vol_get_multichannel_gain_v2_payload_size(me_ptr, gain_list, &req_size); +#ifdef SOFT_VOL_DEBUG + SOFT_VOL_MSG(me_ptr->miid, DBG_MED_PRIO,"num_gains: %lu, req size for the payload is %lu.",num_unique_gains, req_size); +#endif + if (params_ptr->max_data_len >= req_size) + { + result = capi_soft_vol_get_multichannel_gain_v2(me_ptr, params_ptr->data_ptr, gain_list, req_size, num_unique_gains); + } + else + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, + "multichannel volume, Bad param size %lu", + params_ptr->max_data_len); + result = CAPI_ENEEDMORE; + } + } + else + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, + "multichannel volume, Bad param size %lu", + params_ptr->max_data_len); + result = CAPI_ENEEDMORE; + } + params_ptr->actual_data_len = req_size; + break; + } + case PARAM_ID_VOL_CTRL_MULTICHANNEL_MUTE_V2: + { + uint32_t mute_chmask_list[CAPI_CMN_MAX_CHANNEL_MAP_GROUPS] = { 0 }; + uint32_t req_size = 0; + if(params_ptr->max_data_len >= sizeof(volume_ctrl_multichannel_mute_v2_t)) + { + result = capi_soft_vol_get_multichannel_mute_v2_payload_size(me_ptr, mute_chmask_list, &req_size); + if (params_ptr->max_data_len >= req_size) + { + capi_soft_vol_get_multichannel_mute_v2(me_ptr, params_ptr->data_ptr, mute_chmask_list); + } + else + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, + "Get Multichannel Mute, Bad param size %lu", + params_ptr->max_data_len); + params_ptr->actual_data_len = req_size; + result = CAPI_ENEEDMORE; + } + } + else + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, "Get Multichannel Mute, Bad param size %lu", + params_ptr->max_data_len); + return CAPI_ENEEDMORE; + } + params_ptr->actual_data_len = req_size; + break; + } + default: + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, "get, unsupported param ID %#x", param_id); + result |= CAPI_EUNSUPPORTED; + break; + } + } + return result; +} + +capi_err_t capi_soft_vol_set_properties(capi_t *_pif, capi_proplist_t *props_ptr) +{ + capi_soft_vol_t *me_ptr = (capi_soft_vol_t *)_pif; + return capi_soft_vol_process_set_properties(me_ptr, props_ptr); +} + +capi_err_t capi_soft_vol_get_properties(capi_t *_pif, capi_proplist_t *props_ptr) +{ + capi_soft_vol_t *me_ptr = (capi_soft_vol_t *)_pif; + return capi_soft_vol_process_get_properties(me_ptr, props_ptr); +} diff --git a/modules/processing/volume_control/capi/soft_vol/src/capi_soft_vol_island.cpp b/modules/processing/volume_control/capi/soft_vol/src/capi_soft_vol_island.cpp new file mode 100644 index 0000000..cfdd162 --- /dev/null +++ b/modules/processing/volume_control/capi/soft_vol/src/capi_soft_vol_island.cpp @@ -0,0 +1,87 @@ +/* ========================================================================= + * Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + * SPDX-License-Identifier: BSD-3-Clause-Clear + * =========================================================================*/ + +/** + * @file capi_soft_vol_island.cpp + * + * Implementation for soft_vol module + */ + +#include "capi_soft_vol.h" +#include "capi_soft_vol_utils.h" + +// Use to override AR_MSG with AR_MSG_ISLAND. Always include this after ar_msg.h +#ifdef AR_MSG_IN_ISLAND +#include "ar_msg_island_override.h" +#endif + +//#define SOFT_VOL_DEBUG 1 + +#ifdef DO_SOFT_VOL_PROFILING +#include +#endif + +static capi_vtbl_t capi_soft_vol_vtbl = { capi_soft_vol_process, capi_soft_vol_end, + capi_soft_vol_set_param, capi_soft_vol_get_param, + capi_soft_vol_set_properties, capi_soft_vol_get_properties }; + +capi_vtbl_t *capi_soft_vol_get_vtbl() +{ + return &capi_soft_vol_vtbl; +} + +capi_err_t capi_soft_vol_process(capi_t * _pif, + capi_stream_data_t *input[], + capi_stream_data_t *output[]) +{ + capi_err_t result = CAPI_EOK; + capi_soft_vol_t *me_ptr = (capi_soft_vol_t *)(_pif); + + POSAL_ASSERT(me_ptr); + POSAL_ASSERT(input[0]); + POSAL_ASSERT(output[0]); + + uint32_t num_in_samples; + uint32_t max_out_buf_size_in_samples; + uint32_t samples_to_process; + uint32_t bytes_to_sample_conv_fac; + uint32_t bytes_per_sample = me_ptr->SoftVolumeControlsLib.GetBytesPerSample(); + + capi_buf_t *soft_vol_input = input[0]->buf_ptr; + capi_buf_t *soft_vol_output = output[0]->buf_ptr; + + bytes_to_sample_conv_fac = bytes_per_sample >> 1; + + num_in_samples = soft_vol_input[0].actual_data_len >> bytes_to_sample_conv_fac; + max_out_buf_size_in_samples = soft_vol_output[0].max_data_len >> bytes_to_sample_conv_fac; + + samples_to_process = num_in_samples < max_out_buf_size_in_samples ? num_in_samples : max_out_buf_size_in_samples; + + for (uint32_t i = 0; i < me_ptr->soft_vol_lib.numChannels; i++) + { + me_ptr->SoftVolumeControlsLib + .Process(soft_vol_input[i].data_ptr, + soft_vol_output[i].data_ptr, + samples_to_process, + me_ptr->soft_vol_lib.pPerChannelData[me_ptr->soft_vol_lib.channelMapping[i]]); + + soft_vol_input[i].actual_data_len = samples_to_process << bytes_to_sample_conv_fac; + soft_vol_output[i].actual_data_len = samples_to_process << bytes_to_sample_conv_fac; + } + + if (me_ptr->update_gain_over_imcl) + { + capi_soft_vol_send_gain_over_imcl(me_ptr); + me_ptr->update_gain_over_imcl = FALSE; + } + + output[0]->flags = input[0]->flags; + if (input[0]->flags.is_timestamp_valid) + { + output[0]->timestamp = input[0]->timestamp; + } + AR_MSG(DBG_HIGH_PRIO,"sv_end"); + return result; +} diff --git a/modules/processing/volume_control/capi/soft_vol/src/capi_soft_vol_utils.cpp b/modules/processing/volume_control/capi/soft_vol/src/capi_soft_vol_utils.cpp new file mode 100644 index 0000000..d365408 --- /dev/null +++ b/modules/processing/volume_control/capi/soft_vol/src/capi_soft_vol_utils.cpp @@ -0,0 +1,1225 @@ +/* ========================================================================= + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause-Clear + * =========================================================================*/ + +/** + * @file capi_soft_vol_utils.cpp + * + * Utility functions for soft_vol module. + */ + +#include "ar_defs.h" +#include "audpp_util.h" +#include "capi_soft_vol_utils.h" +#include "audio_basic_op_ext.h" +#include "audio_exp10.h" + +static uint32_t mapping_to_mask(const uint32_t numChannels, const uint16_t mapping[]); +static bool_t is_valid_channel_type(const uint16_t channel_type); + +//#define SOFT_VOL_DEBUG 1 + +/* TODO: check for correct value based on nch and fs */ +static const uint32_t SOFT_VOL_STACK_SIZE = 2000; +/*this is for mono, 48K, 16/24bit case */ +static const uint32_t SOFT_VOL_KPPS = 125; + +capi_err_t capi_vol_imc_set_param_handler(capi_soft_vol *me_ptr, capi_buf_t *intent_buf_ptr) +{ + capi_err_t result = CAPI_EOK; + + if (NULL == intent_buf_ptr->data_ptr) + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, "IMC set param handler received null buffer"); + result |= CAPI_EBADPARAM; + return result; + } + + // Level 1 check + if (intent_buf_ptr->actual_data_len < MIN_INCOMING_IMCL_PARAM_SIZE_P_EQ_VOL) + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, "Invalid payload size for incoming data %d", intent_buf_ptr->actual_data_len); + return CAPI_ENEEDMORE; + } + + // accessing the wrong payload.. need to do + sizeof(incoming payload struct to access the actual data) + int8_t * payload_ptr = intent_buf_ptr->data_ptr + sizeof(intf_extn_param_id_imcl_incoming_data_t); + uint32_t payload_size = intent_buf_ptr->actual_data_len - sizeof(intf_extn_param_id_imcl_incoming_data_t); + + while (payload_size >= sizeof(vol_imcl_header_t)) + { + vol_imcl_header_t *header_ptr = (vol_imcl_header_t *)payload_ptr; + + payload_ptr += sizeof(vol_imcl_header_t); + payload_size -= sizeof(vol_imcl_header_t);//TODO: move them to bottom + switch (header_ptr->opcode) + { + case PARAM_ID_P_EQ_VOL_HEADROOM: + { + if (header_ptr->actual_data_len < sizeof(p_eq_vol_headroom_param_t)) + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, + "IMC Param id 0x%lx Invalid payload size for incoming data %d", + header_ptr->opcode, + header_ptr->actual_data_len); + return CAPI_ENEEDMORE; + } + + p_eq_vol_headroom_param_t *cfg_ptr = (p_eq_vol_headroom_param_t *)payload_ptr; + int32_t headroom_mB = (int32_t)cfg_ptr->headroom_in_millibels; + SOFT_VOL_MSG(me_ptr->miid, DBG_HIGH_PRIO, + "IMC Param for headroom incoming data %d", + headroom_mB); + if (me_ptr->soft_vol_lib.headroom_mB != headroom_mB) + { + me_ptr->soft_vol_lib.headroom_mB = headroom_mB; + me_ptr->adjust_volume_based_on_headroom = (int8_t)TRUE; + } + else + { + me_ptr->adjust_volume_based_on_headroom = (int8_t)FALSE; + } + break; + } + case PARAM_ID_IMCL_MUTE: + { + if (header_ptr->actual_data_len < sizeof(param_id_imcl_mute_t)) + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, + "IMC Param id 0x%lx Invalid payload size for incoming data %d", + header_ptr->opcode, + header_ptr->actual_data_len); + return CAPI_ENEEDMORE; + } + + param_id_imcl_mute_t *mute_cfg_ptr = (param_id_imcl_mute_t *)payload_ptr; + + if (TRUE == mute_cfg_ptr->mute_flag) + { + capi_soft_vol_set_mute_for_all_channels(me_ptr, TRUE); + SOFT_VOL_MSG(me_ptr->miid, DBG_HIGH_PRIO, "Mute received through IMC, so muting channels"); + } + else + { + capi_soft_vol_set_mute_for_all_channels(me_ptr, FALSE); + SOFT_VOL_MSG(me_ptr->miid, DBG_HIGH_PRIO, "Un-mute received through IMC, so un-muting channels"); + } + break; + } + default: + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, "Unsupported opcode for incoming data over IMCL %d", header_ptr->opcode); + return CAPI_EUNSUPPORTED; + } + + SOFT_VOL_MSG(me_ptr->miid, DBG_HIGH_PRIO, + "IMC Set param 0x%x done. payload size = %lu", + header_ptr->opcode, + header_ptr->actual_data_len); + } + + payload_ptr += header_ptr->actual_data_len; + payload_size -= header_ptr->actual_data_len; + } + + return result; +} + + +bool_t capi_soft_vol_is_supported_media_type_v2(capi_soft_vol_t *me_ptr, capi_media_fmt_v2_t *format_ptr) +{ + if (CAPI_FIXED_POINT != format_ptr->header.format_header.data_format) + { + AR_MSG(DBG_ERROR_PRIO, + "CAPI SOFT_VOL: unsupported data format %lu", + (uint32_t)format_ptr->header.format_header.data_format); + return FALSE; + } + + if ((16 != format_ptr->format.bits_per_sample) && (32 != format_ptr->format.bits_per_sample)) + { + AR_MSG(DBG_ERROR_PRIO, + "CAPI SOFT_VOL: Only 16/32 bit data supported. Received %lu.", + format_ptr->format.bits_per_sample); + return FALSE; + } + + if (CAPI_DEINTERLEAVED_UNPACKED != format_ptr->format.data_interleaving && format_ptr->format.num_channels != 1) + { + AR_MSG(DBG_ERROR_PRIO, "CAPI SOFT_VOL: Interleaved data not supported."); + return FALSE; + } + + if (!format_ptr->format.data_is_signed) + { + AR_MSG(DBG_ERROR_PRIO, "CAPI SOFT_VOL: Unsigned data not supported."); + return FALSE; + } + + if (format_ptr->format.num_channels > SOFT_VOL_MAX_OUT_CHANNELS) + { + AR_MSG(DBG_ERROR_PRIO, "CAPI SOFT_VOL: Only upto %lu channels supported. Received %lu.", CAPI_MAX_CHANNELS_V2, format_ptr->format.num_channels); + return FALSE; + } + + for (uint16_t i = 0; i < format_ptr->format.num_channels && i < CAPI_MAX_CHANNELS_V2; i++) + { + if (!is_valid_channel_type(format_ptr->channel_type[i])) + { + AR_MSG(DBG_ERROR_PRIO, + "CAPI SOFT_VOL: unsupported channel map %hu for channel no. %hu.", + format_ptr->channel_type[i], + i); + return FALSE; + } + if(format_ptr->channel_type[i] > PCM_MAX_CHANNEL_MAP) + { + //If a higher channel map in the media format occurs even once in the execution history, + //the flag will be set to true from that point onward. + me_ptr->higher_channel_map_present = TRUE; + } + } + + return TRUE; +} + +capi_err_t capi_set_default_ramp_params(capi_soft_vol_t *me_ptr) +{ + capi_err_t result = CAPI_EOK; + volume_ctrl_gain_ramp_params_t default_gain_ramp_params; + default_gain_ramp_params.period_ms = 40; + default_gain_ramp_params.ramping_curve = RAMP_LINEAR; + default_gain_ramp_params.step_us = 1000; + + capi_port_info_t capiv2_pause_port_info; + capiv2_pause_port_info.is_valid = FALSE; + capi_buf_t i_buf; + + i_buf.data_ptr = (int8_t *)&default_gain_ramp_params; + i_buf.actual_data_len = sizeof(default_gain_ramp_params); + i_buf.max_data_len = sizeof(default_gain_ramp_params); + result = me_ptr->vtbl_ptr->set_param((capi_t *)me_ptr, + PARAM_ID_VOL_CTRL_GAIN_RAMP_PARAMETERS, + &capiv2_pause_port_info, + &i_buf); + + volume_ctrl_mute_ramp_params_t default_mute_ramp_params; + default_mute_ramp_params.period_ms = 40; + default_mute_ramp_params.ramping_curve = RAMP_LINEAR; + default_mute_ramp_params.step_us = 1000; + + i_buf.data_ptr = (int8_t *)&default_mute_ramp_params; + i_buf.actual_data_len = sizeof(default_mute_ramp_params); + i_buf.max_data_len = sizeof(default_mute_ramp_params); + result = me_ptr->vtbl_ptr->set_param((capi_t *)me_ptr, + PARAM_ID_VOL_CTRL_MUTE_RAMP_PARAMETERS, + &capiv2_pause_port_info, + &i_buf); + return result; +} + +capi_err_t capi_soft_vol_init_lib_memory(capi_soft_vol_t *me_ptr) +{ + /* TODO: Memory query return or memory allocation for required channels. */ + int8_t *ptr = (int8_t *)me_ptr; + + me_ptr->soft_vol_lib.masterGain = UNITY_GAIN_Q13; + me_ptr->soft_vol_lib.headroom_mB = 0; + me_ptr->soft_vol_lib.channelGain[0] = 0; + me_ptr->soft_vol_lib.softPauseEnable = FALSE; + /* The 0th index channel is invalid, so don't allocate memory for it */ + me_ptr->soft_vol_lib.pPerChannelData[0] = NULL; + me_ptr->soft_vol_lib.numChannels = VOLUME_CONTROL_MAX_CHANNELS_V2; + for (uint32_t i = 0; i < me_ptr->soft_vol_lib.numChannels; i++) + { + /* Initialize channel struct array to appropriate values. */ + me_ptr->soft_vol_lib.channelMapping[i] = i + 1; + } + + CSoftVolumeControlsLib *pLib __attribute__((unused)); + posal_memory_placement_new(pLib, &me_ptr->SoftVolumeControlsLib, CSoftVolumeControlsLib); + + uint32_t perChannelStructSize = align_to_8_byte(CSoftVolumeControlsLib::GetSizeOfPerChannelStruct()); + const uint32_t defaultGainQ28 = capi_soft_vol_calc_gain_q28(me_ptr->soft_vol_lib.masterGain, UNITY_GAIN_Q28); + + ptr += align_to_8_byte(sizeof(capi_soft_vol_t)); + + for (uint32_t i = MIN_CHANNEL_TYPE; i <= MAX_CHANNEL_TYPE; i++) + { + me_ptr->soft_vol_lib.channelGain[i] = UNITY_GAIN_Q28; + + me_ptr->soft_vol_lib.pPerChannelData[i] = ptr; + me_ptr->SoftVolumeControlsLib.InitializePerChannelStruct(me_ptr->soft_vol_lib.pPerChannelData[i]); + me_ptr->SoftVolumeControlsLib.SetVolume(defaultGainQ28, me_ptr->soft_vol_lib.pPerChannelData[i]); + ptr += perChannelStructSize; + } + return CAPI_EOK; +} + +capi_err_t capi_soft_vol_set_input_media_format_v2(capi_soft_vol_t *me_ptr, capi_buf_t *prop_payload_ptr) +{ + capi_err_t result = CAPI_EOK; + uint32_t required_size = CAPI_SOFT_VOL_MF_V2_MIN_SIZE; + if (prop_payload_ptr->actual_data_len < required_size) + { + SOFT_VOL_MSG(me_ptr->miid, DBG_HIGH_PRIO, + "Set property for INPUT MEDIA FORMAT" + "failed due to bad size %lu", + prop_payload_ptr->actual_data_len); + return CAPI_EBADPARAM; + } + capi_media_fmt_v2_t *data_ptr = (capi_media_fmt_v2_t *)(prop_payload_ptr->data_ptr); + + if (data_ptr->format.minor_version < CAPI_MEDIA_FORMAT_MINOR_VERSION) + { + SOFT_VOL_MSG(me_ptr->miid, DBG_HIGH_PRIO, + "Set property for INPUT MEDIA FORMAT" + "failed due to unsupported version %lu", + data_ptr->format.minor_version); + return CAPI_EUNSUPPORTED; + } + if (!capi_soft_vol_is_supported_media_type_v2(me_ptr, data_ptr)) + return CAPI_EUNSUPPORTED; + + required_size += data_ptr->format.num_channels * sizeof(data_ptr->channel_type[0]); + if (prop_payload_ptr->actual_data_len < required_size) + { + SOFT_VOL_MSG(me_ptr->miid, DBG_HIGH_PRIO, + "Set property for INPUT MEDIA FORMAT" + "failed due to bad size %lu", + prop_payload_ptr->actual_data_len); + return CAPI_EBADPARAM; + } + + me_ptr->input_media_fmt.header = data_ptr->header; + me_ptr->input_media_fmt.format = data_ptr->format; + memscpy(me_ptr->input_media_fmt.channel_type, + sizeof(me_ptr->input_media_fmt.channel_type), + data_ptr->channel_type, + data_ptr->format.num_channels * sizeof(data_ptr->channel_type[0])); + me_ptr->input_media_fmt.format.minor_version = CAPI_MEDIA_FORMAT_MINOR_VERSION; + + me_ptr->output_media_fmt = me_ptr->input_media_fmt; + capi_soft_vol_set_sample_rate(me_ptr, me_ptr->input_media_fmt.format.sampling_rate); + me_ptr->SoftVolumeControlsLib.SetBytesPerSample(me_ptr->input_media_fmt.format.bits_per_sample >> 3); + + me_ptr->soft_vol_lib.numChannels = me_ptr->input_media_fmt.format.num_channels; + + const uint32_t channelMappingLen = + me_ptr->input_media_fmt.format.num_channels * sizeof(me_ptr->soft_vol_lib.channelMapping[0]); + memscpy(me_ptr->soft_vol_lib.channelMapping, + sizeof(me_ptr->soft_vol_lib.channelMapping), + me_ptr->input_media_fmt.channel_type, + channelMappingLen); + me_ptr->soft_vol_state = SOFT_VOL_ENABLE; + + if (me_ptr->adjust_volume_based_on_headroom) + (void)capi_soft_vol_headroom_gain(me_ptr, me_ptr->soft_vol_lib.headroom_mB); + + if (SOFT_VOL_DISABLE != me_ptr->soft_vol_state) + { + result = capi_cmn_update_algo_delay_event(&me_ptr->cb_info, 0); + + uint32_t default_kpps = SOFT_VOL_KPPS; + uint32_t default_sample_rate = 48000; + uint32_t kpps = ((me_ptr->input_media_fmt.format.num_channels * default_kpps * + (uint64_t)me_ptr->input_media_fmt.format.sampling_rate) / + default_sample_rate); + + result = capi_cmn_update_kpps_event(&me_ptr->cb_info, kpps); + result = capi_cmn_update_bandwidth_event(&me_ptr->cb_info, 0, 0); + } + if (SOFT_VOL_ENABLE == me_ptr->soft_vol_state) + { + uint32_t process_check = 0; + for (uint32_t i = 0; i < me_ptr->soft_vol_lib.numChannels; i++) + { + if (!me_ptr->SoftVolumeControlsLib.isUnityGain( + me_ptr->soft_vol_lib.pPerChannelData[me_ptr->soft_vol_lib.channelMapping[i]])) + { + process_check = 1; + break; + } + } + result = capi_cmn_update_process_check_event(&me_ptr->cb_info, process_check); + } + + result = capi_cmn_output_media_fmt_event_v2(&me_ptr->cb_info, &me_ptr->output_media_fmt, FALSE, 0); + return result; +} + +capi_err_t capi_soft_vol_process_set_properties(capi_soft_vol_t *me_ptr, capi_proplist_t *proplist_ptr) +{ + capi_err_t capi_result = CAPI_EOK; + if (NULL == me_ptr) + { + SOFT_VOL_MSG(MIID_UNKNOWN, DBG_ERROR_PRIO, "Set common property received null ptr"); + return CAPI_EBADPARAM; + } + + capi_result |= capi_cmn_set_basic_properties(proplist_ptr, + (capi_heap_id_t *)&me_ptr->heap_id, + &me_ptr->cb_info, + TRUE); + uint32_t miid = me_ptr ? me_ptr->miid : MIID_UNKNOWN; + if (CAPI_EOK != capi_result) + { + SOFT_VOL_MSG(miid, DBG_ERROR_PRIO, "Set basic properties failed with result %lu", capi_result); + } + + capi_prop_t *prop_array = proplist_ptr->prop_ptr; + uint32_t i; + + for (i = 0; i < proplist_ptr->props_num; i++) + { + capi_buf_t *payload_ptr = &(prop_array[i].payload); + miid = me_ptr ? me_ptr->miid : MIID_UNKNOWN; + + switch (prop_array[i].id) + { + case CAPI_EVENT_CALLBACK_INFO: + case CAPI_PORT_NUM_INFO: + case CAPI_HEAP_ID: + case CAPI_CUSTOM_INIT_DATA: + case CAPI_ALGORITHMIC_RESET: + case CAPI_INTERFACE_EXTENSIONS: + { + break; + } + case CAPI_INPUT_MEDIA_FORMAT_V2: + { + SOFT_VOL_MSG(miid, DBG_HIGH_PRIO, "received input media fmt V2"); + capi_result |= capi_soft_vol_set_input_media_format_v2(me_ptr, payload_ptr); + 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; + SOFT_VOL_MSG(miid, DBG_LOW_PRIO, + "This module-id 0x%08lX, instance-id 0x%08lX", + data_ptr->module_id, + me_ptr->miid); + } + else + { + SOFT_VOL_MSG(miid, DBG_ERROR_PRIO, + "Set property id 0x%lx, Bad param size %lu", + prop_array[i].id, + payload_ptr->max_data_len); + capi_result |= CAPI_ENEEDMORE; + } + break; + } // CAPI_MODULE_INSTANCE_ID + default: + { + capi_result |= CAPI_EUNSUPPORTED; + continue; + } + } + } + return capi_result; +} + +capi_err_t capi_soft_vol_process_get_properties(capi_soft_vol_t *me_ptr, capi_proplist_t *proplist_ptr) +{ + uint32_t i; + capi_err_t result = CAPI_EOK; + + uint32_t soft_vol_lib_size = align_to_8_byte(CSoftVolumeControlsLib::GetSizeOfPerChannelStruct()); + soft_vol_lib_size *= (MAX_CHANNEL_TYPE - MIN_CHANNEL_TYPE + 1); + + capi_basic_prop_t mod_prop; + mod_prop.init_memory_req = align_to_8_byte(sizeof(capi_soft_vol_t)) + align_to_8_byte(soft_vol_lib_size); + mod_prop.stack_size = SOFT_VOL_STACK_SIZE; + mod_prop.num_fwk_extns = 0; + mod_prop.fwk_extn_ids_arr = NULL; + mod_prop.is_inplace = TRUE; + mod_prop.req_data_buffering = FALSE; + mod_prop.max_metadata_size = 0; + + result |= capi_cmn_get_basic_properties(proplist_ptr, &mod_prop); + uint32_t miid = me_ptr ? me_ptr->miid : MIID_UNKNOWN; + + if (CAPI_EOK != result) + { + SOFT_VOL_MSG(miid, DBG_ERROR_PRIO, "Get common basic properties failed with result %lu", result); + } + + capi_prop_t *prop_array = proplist_ptr->prop_ptr; + + for (i = 0; i < proplist_ptr->props_num; i++) + { + miid = me_ptr ? me_ptr->miid : MIID_UNKNOWN; + + switch (prop_array[i].id) + { + case CAPI_INIT_MEMORY_REQUIREMENT: + case CAPI_STACK_SIZE: + case CAPI_IS_INPLACE: + case CAPI_REQUIRES_DATA_BUFFERING: + case CAPI_OUTPUT_MEDIA_FORMAT_SIZE: + case CAPI_NUM_NEEDED_FRAMEWORK_EXTENSIONS: + case CAPI_NEEDED_FRAMEWORK_EXTENSIONS: + { + break; + } + case CAPI_IS_ELEMENTARY: + { + capi_buf_t *payload_ptr = &prop_array[i].payload; + + if (payload_ptr->max_data_len >= sizeof(capi_is_elementary_t)) + { + capi_is_elementary_t *data_ptr = (capi_is_elementary_t *)payload_ptr->data_ptr; + data_ptr->is_elementary = TRUE; + payload_ptr->actual_data_len = sizeof(capi_is_elementary_t); + } + else + { + CAPI_SET_ERROR(result, CAPI_ENEEDMORE); + payload_ptr->actual_data_len = 0; + SOFT_VOL_MSG(miid, DBG_ERROR_PRIO, "Insufficient get property size."); + break; + } + break; + } + case CAPI_INTERFACE_EXTENSIONS: + { + capi_buf_t * payload_ptr = &prop_array[i].payload; + capi_interface_extns_list_t *intf_ext_list = (capi_interface_extns_list_t *)payload_ptr->data_ptr; + result |= + ((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(result)) + { + payload_ptr->actual_data_len = 0; + SOFT_VOL_MSG(miid, DBG_ERROR_PRIO, "Insufficient get property size."); + 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; + } + case CAPI_OUTPUT_MEDIA_FORMAT_V2: + { + if (NULL == me_ptr) + { + SOFT_VOL_MSG(MIID_UNKNOWN, DBG_ERROR_PRIO, "null ptr while querying output mf"); + return CAPI_EBADPARAM; + } + result = capi_cmn_handle_get_output_media_fmt_v2(&prop_array[i], &me_ptr->output_media_fmt); + break; + } + case CAPI_PORT_DATA_THRESHOLD: + { + if (NULL == me_ptr) + { + SOFT_VOL_MSG(MIID_UNKNOWN, DBG_ERROR_PRIO, "null ptr while querying threshold"); + return CAPI_EBADPARAM; + } + uint32_t threshold_in_bytes = 1; // default + result = capi_cmn_handle_get_port_threshold(&prop_array[i], threshold_in_bytes); + break; + } + default: + { + result |= CAPI_EUNSUPPORTED; + continue; + } + } + } + return result; +} + +void capi_set_soft_stepping_param(const capi_soft_vol_t *me_ptr, + SoftSteppingParams * pParams, + uint32_t period, + uint32_t step, + uint32_t rampingCurve) +{ + if (period < MIN_PERIOD) + { + period = (int32_t)MIN_PERIOD; + } + else if (period > MAX_PERIOD) + { + period = (int32_t)MAX_PERIOD; + } + + if (step < MIN_STEP) + { + step = (int32_t)MIN_STEP; + } + else if (step > MAX_STEP) + { + step = (int32_t)MAX_STEP; + } + + pParams->periodMs = period; + pParams->stepUs = step; + + switch (rampingCurve) + { + case PARAM_VOL_CTRL_RAMPINGCURVE_LINEAR: + { + pParams->rampingCurve = RAMP_LINEAR; + break; + } + case PARAM_VOL_CTRL_RAMPINGCURVE_EXP: + { + pParams->rampingCurve = RAMP_EXP; + break; + } + case PARAM_VOL_CTRL_RAMPINGCURVE_LOG: + { + pParams->rampingCurve = RAMP_LOG; + break; + } + case PARAM_VOL_CTRL_RAMPINGCURVE_FRAC_EXP: + { + pParams->rampingCurve = RAMP_FRACT_EXP; + break; + } + default: + { + pParams->rampingCurve = RAMP_LINEAR; + break; + } + } /* switch (rampingCurve) */ +} + +void capi_get_soft_stepping_param(const capi_soft_vol_t *me_ptr, + const SoftSteppingParams *pParams, + uint32_t * pPeriod, + uint32_t * pStep, + uint32_t * pRampingCurve) +{ + *pPeriod = pParams->periodMs; + *pStep = pParams->stepUs; + + switch (pParams->rampingCurve) + { + case RAMP_LINEAR: + { + *pRampingCurve = PARAM_VOL_CTRL_RAMPINGCURVE_LINEAR; + break; + } + case RAMP_EXP: + { + *pRampingCurve = PARAM_VOL_CTRL_RAMPINGCURVE_EXP; + break; + } + case RAMP_LOG: + { + *pRampingCurve = PARAM_VOL_CTRL_RAMPINGCURVE_LOG; + break; + } + case RAMP_FRACT_EXP: + { + *pRampingCurve = PARAM_VOL_CTRL_RAMPINGCURVE_FRAC_EXP; + break; + } + default: + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, + "Unsupported ramping curve found, defaulting to PARAM_VOL_CTRL_RAMPINGCURVE_LINEAR"); + *pRampingCurve = PARAM_VOL_CTRL_RAMPINGCURVE_LINEAR; + break; + } + } +} + +/* Returns FALSE if same channel is set to multiple times in different configs */ +bool_t check_channel_mask_softvol(uint8_t *vol_param_ptr, uint32_t param_id) +{ + uint32_t num_config = 0; + uint64_t check_channel_mask = 0x0; + uint64_t current_channel_mask = 0x0; + bool_t check = TRUE; + uint32_t offset = memscpy(&num_config, sizeof(uint32_t), vol_param_ptr, sizeof(uint32_t)); + uint8_t *data_ptr = vol_param_ptr; + + for (uint32_t cnfg_cntr = 0; cnfg_cntr < num_config; cnfg_cntr++) + { + //Increment data ptr by calculated offset to point at next payload_lsw + data_ptr = vol_param_ptr + offset; + + // present configuration + //offset increments by 8 + offset += memscpy(¤t_channel_mask, sizeof(uint64_t), data_ptr, sizeof(uint64_t)); + + switch (param_id) + { + default: + { + //offset increments by 4 + offset += sizeof(uint32_t); + } + } + + // need to ignore 0th bit + if ((0 == (check_channel_mask & current_channel_mask)) || (1 == (check_channel_mask & current_channel_mask))) + { + check_channel_mask |= current_channel_mask; + } + else + { + check = FALSE; + return check; + } + } + return check; +} + +capi_err_t capi_soft_vol_set_multichannel_gain(capi_soft_vol_t * me_ptr, + volume_ctrl_multichannel_gain_t *vol_payload_ptr) +{ + uint64_t channel_mask = 0; + // validating the configs + if (!check_channel_mask_softvol((uint8_t *)vol_payload_ptr, PARAM_ID_VOL_CTRL_MULTICHANNEL_GAIN)) + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, "Trying to set different gain values to same channel, returning"); + return CAPI_EBADPARAM; + } + + // loop through all the configs + for (uint32_t cnfg_cntr = 0; cnfg_cntr < vol_payload_ptr->num_config; cnfg_cntr++) + { + channel_mask = (uint64_t)vol_payload_ptr->gain_data[cnfg_cntr].channel_mask_msb << 32 | + vol_payload_ptr->gain_data[cnfg_cntr].channel_mask_lsb; + + if (channel_mask & 0x1) + { + SOFT_VOL_MSG(me_ptr->miid, DBG_MED_PRIO,"Warning! Set multichannel gain: reserved bit(LSB) is being set to 1"); + } + // setting the gain data channel wise + for (uint32_t j = 1; j < (VOLUME_CONTROL_MAX_CHANNELS + 1); j++) + { + channel_mask = channel_mask >> 1; + if (channel_mask & 0x1) + { + me_ptr->soft_vol_lib.channelGain[j] = vol_payload_ptr->gain_data[cnfg_cntr].gain; + uint32 gainQ28 = + capi_soft_vol_calc_gain_q28(me_ptr->soft_vol_lib.masterGain, me_ptr->soft_vol_lib.channelGain[j]); + me_ptr->SoftVolumeControlsLib.SetVolume(gainQ28, me_ptr->soft_vol_lib.pPerChannelData[j]); + +#ifdef SOFT_VOL_DEBUG + SOFT_VOL_MSG(me_ptr->miid, DBG_HIGH_PRIO, + "Set volume: channel: %hu Gain: 0x%lx", + j, + me_ptr->soft_vol_lib.channelGain[j]); +#endif + } + } + } + return CAPI_EOK; +} + +capi_err_t capi_soft_vol_set_multichannel_mute(capi_soft_vol_t * me_ptr, + volume_ctrl_multichannel_mute_t *mute_payload_ptr) +{ + uint64_t channel_mask = 0; + // validating the configs + if (!check_channel_mask_softvol((uint8_t *)mute_payload_ptr, PARAM_ID_VOL_CTRL_MULTICHANNEL_MUTE)) + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, "Trying to set different mute configurations to same channel, returning "); + return CAPI_EBADPARAM; + } + + // loop through all the configs + for (uint32_t cnfg_cntr = 0; cnfg_cntr < mute_payload_ptr->num_config; cnfg_cntr++) + { + channel_mask = (uint64_t)mute_payload_ptr->mute_data[cnfg_cntr].channel_mask_msb << 32 | + mute_payload_ptr->mute_data[cnfg_cntr].channel_mask_lsb; + + if (channel_mask & 0x1) + { + SOFT_VOL_MSG(me_ptr->miid, DBG_LOW_PRIO,"Warning Set mute: reserve bit is set 1"); + } + // setting the mute data channel wise + for (uint32_t j = 1; j < (VOLUME_CONTROL_MAX_CHANNELS + 1); j++) + { + channel_mask = channel_mask >> 1; + if (channel_mask & 0x1) + { + if (0 == mute_payload_ptr->mute_data[cnfg_cntr].mute) + { + me_ptr->SoftVolumeControlsLib.Unmute(me_ptr->soft_vol_lib.pPerChannelData[j]); + } + else + { + me_ptr->SoftVolumeControlsLib.Mute(me_ptr->soft_vol_lib.pPerChannelData[j]); + } +#ifdef SOFT_VOL_DEBUG + SOFT_VOL_MSG(me_ptr->miid, DBG_HIGH_PRIO, + "Set mute: channel: %hu Mute: %lu", + j, + mute_payload_ptr->mute_data[cnfg_cntr].mute); +#endif + } + } + } + return CAPI_EOK; +} + +void capi_soft_vol_get_multichannel_mute(capi_soft_vol_t * me_ptr, + volume_ctrl_multichannel_mute_t *mute_payload_ptr) +{ + uint16_t channel_type = 0; + uint32_t mute = 0; + mute_payload_ptr->num_config = 1; // num_config is 1 (only mute data) + mute_payload_ptr->mute_data[0].channel_mask_lsb = 0; + mute_payload_ptr->mute_data[0].channel_mask_msb = 0; + + for (int i = 0; i < me_ptr->soft_vol_lib.numChannels; i++) + { + channel_type = me_ptr->soft_vol_lib.channelMapping[i]; + mute = (me_ptr->SoftVolumeControlsLib.IsMuted( + me_ptr->soft_vol_lib.pPerChannelData[me_ptr->soft_vol_lib.channelMapping[i]])) + ? 1 + : 0; + if (mute == 1) + { + // getting param of channels 1~31 (channel_mask_lsb) + if (channel_type < SOFT_VOL_MASK_WIDTH) + { + mute_payload_ptr->mute_data[0].channel_mask_lsb = + mute_payload_ptr->mute_data[0].channel_mask_lsb | (1 << channel_type); + } + // getting params of channels 32 ~ Max channel type support ( channel_mask_msb) + else if (channel_type < SOFT_VOL_MAX_CHANNEL_TYPE + 1) + { + mute_payload_ptr->mute_data[0].channel_mask_msb = + mute_payload_ptr->mute_data[0].channel_mask_msb | (1 << (channel_type - 32)); + } + } + + } + if(mute_payload_ptr->mute_data[0].channel_mask_lsb != 0 || mute_payload_ptr->mute_data[0].channel_mask_msb != 0) + { + mute_payload_ptr->mute_data[0].mute = 1; + } + +} + +/* checks if the gain is already present in the previous configs added and updates the number of unique gains + * (equivalent to number of configs)*/ +bool_t check_unique_gain(uint32_t *gain_list, uint32_t gain, uint32_t *num_unique_gains_ptr, uint32_t *position_ptr) +{ + bool_t unique = TRUE; + // loop through all the gains in the gain_list + for (uint32_t i = 0; i < *num_unique_gains_ptr; i++) + { + if (gain == gain_list[i]) + { + unique = FALSE; + *position_ptr = i; + break; + } + } + if (unique) + { + gain_list[*num_unique_gains_ptr] = gain; + *position_ptr = *num_unique_gains_ptr; + *num_unique_gains_ptr += 1 ; + } + return unique; +} + +static void capi_soft_vol_get_target_gain_config(capi_soft_vol_t *me_ptr, param_id_imcl_gain_t *vol_payload_ptr) +{ + uint32_t position = 0; + uint32_t gain = 0; + uint16_t channel_type = 0; + vol_payload_ptr->num_config = 0; + + for (uint32_t i = 0; i < me_ptr->soft_vol_lib.numChannels; i++) + { + channel_type = me_ptr->soft_vol_lib.channelMapping[i]; + gain = me_ptr->SoftVolumeControlsLib.GetTargetGain(me_ptr->soft_vol_lib.pPerChannelData[channel_type]); + position = vol_payload_ptr->num_config; + for (uint32_t j = 0; j < vol_payload_ptr->num_config; j++) + { + if (gain == vol_payload_ptr->gain_data[j].gain) + { + position = j; + break; + } + } + if (position == vol_payload_ptr->num_config) + { + vol_payload_ptr->num_config += 1; + vol_payload_ptr->gain_data[position].gain = gain; + vol_payload_ptr->gain_data[position].channel_mask_lsb = 0; + vol_payload_ptr->gain_data[position].channel_mask_msb = 0; + } + + // getting param of channels 1~31 (channel_mask_lsb) + if (channel_type < SOFT_VOL_MASK_WIDTH) + { + vol_payload_ptr->gain_data[position].channel_mask_lsb |= (1 << channel_type); + } + // getting params of channels 32 ~ Max channel support ( channel_mask_msb) + else + { + vol_payload_ptr->gain_data[position].channel_mask_msb |= (1 << (channel_type - SOFT_VOL_MASK_WIDTH)); + } + } +} + +uint32_t capi_soft_vol_get_multichannel_gain(capi_soft_vol_t * me_ptr, + volume_ctrl_multichannel_gain_t *vol_payload_ptr) +{ + uint32_t position = 0; + uint32_t num_unique_gains = 0; // num_unique_gains is equivalent to num_configs + uint32_t gain_list[VOLUME_CONTROL_MAX_CHANNELS] = { 0 }; // list to maintain all unique gains + uint32_t gain = 0; + uint16_t channel_type = 0; + bool_t unique = TRUE; + + if (vol_payload_ptr) + { + vol_payload_ptr->num_config = 0; + } + + for (uint32_t i = 0; i < me_ptr->soft_vol_lib.numChannels; i++) + { + gain = me_ptr->soft_vol_lib.channelGain[me_ptr->soft_vol_lib.channelMapping[i]]; + unique = check_unique_gain(gain_list, gain, &num_unique_gains, &position); + + if (vol_payload_ptr) + { + if (unique) + { + vol_payload_ptr->num_config += 1; + vol_payload_ptr->gain_data[position].gain = gain; + vol_payload_ptr->gain_data[position].channel_mask_lsb = 0; + vol_payload_ptr->gain_data[position].channel_mask_msb = 0; + } + channel_type = me_ptr->soft_vol_lib.channelMapping[i]; + // getting param of channels 1~31 (channel_mask_lsb) + if (channel_type < SOFT_VOL_MASK_WIDTH) + { + vol_payload_ptr->gain_data[position].channel_mask_lsb = + vol_payload_ptr->gain_data[position].channel_mask_lsb | (1 << channel_type); + } + // getting params of channels 32 ~ Max channel support ( channel_mask_msb) + else if (channel_type < VOLUME_CONTROL_MAX_CHANNELS + 1) + { + vol_payload_ptr->gain_data[position].channel_mask_msb = + vol_payload_ptr->gain_data[position].channel_mask_msb | (1 << (channel_type - 32)); + } + } + } + return num_unique_gains; +} + +void capi_soft_vol_reinit_new_channels(capi_soft_vol_t *me_ptr, + const uint32_t numNewChannels, + const uint16_t newMapping[]) +{ + uint32_t oldChMask = mapping_to_mask(me_ptr->soft_vol_lib.numChannels, me_ptr->soft_vol_lib.channelMapping); + uint32_t newChMask = mapping_to_mask(numNewChannels, newMapping); + + uint32_t additionalChMask = (newChMask & (~(oldChMask))); + + while (additionalChMask != 0) + { + int32_t channelType = s32_get_lsb_s32(additionalChMask); + me_ptr->SoftVolumeControlsLib.GoToTargetGainImmediately(me_ptr->soft_vol_lib.pPerChannelData[channelType]); + additionalChMask = s32_clr_bit_s32_s32(additionalChMask, channelType); + } +} + +void capi_soft_vol_set_mute_for_all_channels(capi_soft_vol_t *me_ptr, const uint32_t mute_flag) +{ + if (0 == mute_flag) + { + for (uint32_t i = MIN_CHANNEL_TYPE; i <= MAX_CHANNEL_TYPE; i++) + { + me_ptr->SoftVolumeControlsLib.Unmute(me_ptr->soft_vol_lib.pPerChannelData[i]); + } + } + else + { + for (uint32_t i = MIN_CHANNEL_TYPE; i <= MAX_CHANNEL_TYPE; i++) + { + me_ptr->SoftVolumeControlsLib.Mute(me_ptr->soft_vol_lib.pPerChannelData[i]); + } + } +} + +static uint32_t mapping_to_mask(const uint32_t numChannels, const uint16_t mapping[]) +{ + uint32_t mask = 0; + + for (uint32_t i = 0; i < numChannels; i++) + { + mask = s32_set_bit_s32_s32(mask, mapping[i]); + } + + return mask; +} + +static bool_t is_valid_channel_type(const uint16_t channel_type) +{ + bool_t result = TRUE; + if (channel_type < MIN_CHANNEL_TYPE || channel_type > MAX_CHANNEL_TYPE) + { + result = FALSE; + } + + return result; +} + +uint32 capi_soft_vol_calc_gain_q28(const uint16_t masterGainQ13, const uint32_t channelGainQ28) +{ + /* Combine the channel gain and master gain into a final Q28 gain value */ + uint64 gainQ41 = u64_mult_u32_u32((uint32)masterGainQ13, channelGainQ28); + uint64 gainQ28 = gainQ41 >> (41 - 28); + + /* TODO: Need to saturate it here */ + uint32 MAX_UINT32_VAL = (((uint64)1) << 32) - 1; + uint32 gainL32Q28 = (gainQ28 > MAX_UINT32_VAL) ? MAX_UINT32_VAL : (uint32)gainQ28; + + return gainL32Q28; +} + +int32 capi_soft_vol_convert_headroom_linear_gain(const int32_t reqHeadroom) +{ + int32_t gain_Q28; + int32_t gain_dB; + + /* (1/100 in Q24 format) */ + gain_dB = (int64_t)(-reqHeadroom) * 167772; + + /* Following expression calculates round(10^(gain_dB/20)*2^28) + * Converting the headroom in Q0 format to gain in Q28 format + * Input to exp10_fixed-Q26, output-Q15 + * 1/20 = 13107 (in Q18) */ + gain_Q28 = exp10_fixed(s32_saturate_s64(s64_mult_fp_s32_s16_shift(gain_dB, 13107, 0))) << 13; + + /* Minimum and maximum values are taken as -96 dB(round(10^(-96/20)*2^28) == 4254) + * and 0 dB(round(10^(0/20)*2^28) == 268435456) respectively (in Q28 format) + * as per Google EQ standards */ + + if (gain_Q28 < HEADROOM_GAIN_Q28_MIN) + { + gain_Q28 = HEADROOM_GAIN_Q28_MIN; + } + else if (gain_Q28 > HEADROOM_GAIN_Q28_MAX) + { + gain_Q28 = HEADROOM_GAIN_Q28_MAX; + } + + return gain_Q28; +} + +capi_err_t capi_soft_vol_headroom_gain(capi_soft_vol_t *me_ptr, int32_t headroom_mB) +{ + capi_err_t result = AR_EOK; + uint32_t max_gain = 0; + uint32_t gainQ28[VOLUME_CONTROL_MAX_CHANNELS_V2 + 1]; + uint32_t max_gain_with_headroom = 0; + bool_t headroom_flag = 0; + me_ptr->soft_vol_lib.topoReqHeadroom = capi_soft_vol_convert_headroom_linear_gain(headroom_mB); + + /* Comparison below is between the gain applied by the volume module + * and the headroom applied on 0dB gain (In case the gain applied by + * the volume module already assures the needed headroom), + * Determine the max gain amongst all of the channels */ + for (uint8_t i = 0; i < me_ptr->soft_vol_lib.numChannels; i++) + { + gainQ28[i] = + capi_soft_vol_calc_gain_q28(me_ptr->soft_vol_lib.masterGain, + me_ptr->soft_vol_lib.channelGain[me_ptr->soft_vol_lib.channelMapping[i]]); + + if (gainQ28[i] > max_gain) + { + max_gain = gainQ28[i]; + } + } + uint32_t gainQ28_headroom = + (int32_t)((((int64_t)me_ptr->soft_vol_lib.topoReqHeadroom) * HEADROOM_GAIN_Q28_MAX) >> 28); + + /* In case gain needed to provide required headroom is less than the + * worst case/max gain of individual channels, it will be applied on + * all of the channels in the ratio of the (max_gain - headroom) + * to the max_gain */ + if (max_gain > gainQ28_headroom) + { + SOFT_VOL_MSG(me_ptr->miid, DBG_HIGH_PRIO, + "Volume adjustment is required as headroom is less than volume. Max gain: %lu, Gain for headroom: %lu", + max_gain, + gainQ28_headroom); + max_gain_with_headroom = gainQ28_headroom; + headroom_flag = 1; + } + + if ((headroom_flag == 1) && (0 != headroom_mB)) + { + for (uint8_t i = 0; i < me_ptr->soft_vol_lib.numChannels; i++) + { + SOFT_VOL_MSG(me_ptr->miid, DBG_HIGH_PRIO, + "Adjusted gains: Original gain: %lu Gain for headroom: %lu Max: %lu", + gainQ28[i], + max_gain_with_headroom, + max_gain); + gainQ28[i] = (uint32_t)((gainQ28[i] * (uint64_t)max_gain_with_headroom) / (uint64_t)max_gain); + SOFT_VOL_MSG(me_ptr->miid, DBG_HIGH_PRIO, "The new gain value: %lu", gainQ28[i]); + } + } + for (uint8_t i = 0; i < me_ptr->soft_vol_lib.numChannels; i++) + { + me_ptr->SoftVolumeControlsLib + .SetVolume(gainQ28[i], me_ptr->soft_vol_lib.pPerChannelData[me_ptr->soft_vol_lib.channelMapping[i]]); + } + return result; +} + +void capi_soft_vol_set_sample_rate(capi_soft_vol_t *me_ptr, const uint32_t sample_rate) +{ + uint32_t old_sample_rate = me_ptr->SoftVolumeControlsLib.GetSampleRate(); + + for (uint32_t i = MIN_CHANNEL_TYPE; i <= MAX_CHANNEL_TYPE; i++) + { + me_ptr->SoftVolumeControlsLib.SetSampleRate(old_sample_rate, + sample_rate, + me_ptr->soft_vol_lib.pPerChannelData[i]); + } +} + +void capi_soft_vol_send_gain_over_imcl(capi_soft_vol_t *me_ptr) +{ + capi_err_t result = CAPI_EOK; + capi_buf_t buf; + uint32_t control_port_id = 0; + imcl_port_state_t port_state = CTRL_PORT_CLOSE; + ctrl_port_data_t *port_data_ptr = NULL; + buf.actual_data_len = sizeof(imc_param_header_t) + sizeof(param_id_imcl_gain_t) + + me_ptr->soft_vol_lib.numChannels * sizeof(imcl_gain_config_t); + buf.data_ptr = NULL; + buf.max_data_len = 0; + imcl_outgoing_data_flag_t flags; + flags.should_send = TRUE; + flags.is_trigger = FALSE; + + // Get the firt control port id for the intent #INTENT_ID_GAIN_INFO + capi_cmn_ctrl_port_list_get_next_port_data(&me_ptr->ctrl_port_info, + INTENT_ID_GAIN_INFO, + control_port_id, // initially, an invalid port id + &port_data_ptr); + + if (port_data_ptr) + { + control_port_id = port_data_ptr->port_info.port_id; + port_state = port_data_ptr->state; + } + + while (port_data_ptr && control_port_id && (CTRL_PORT_PEER_CONNECTED == port_state)) + { + // 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)) + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, "Getting one time imcl buffer failed"); + return; + } + + imc_param_header_t * out_cfg_ptr = (imc_param_header_t *)buf.data_ptr; + param_id_imcl_gain_t *gain_payload = (param_id_imcl_gain_t *)(out_cfg_ptr + 1); + + capi_soft_vol_get_target_gain_config(me_ptr, gain_payload); + + out_cfg_ptr->opcode = PARAM_ID_IMCL_TARGET_GAIN; + out_cfg_ptr->actual_data_len = + sizeof(param_id_imcl_gain_t) + gain_payload->num_config * sizeof(imcl_gain_config_t); + + // Send data ready to the peer module + if (CAPI_SUCCEEDED(capi_cmn_imcl_send_to_peer(&me_ptr->cb_info, &buf, control_port_id, flags))) + { + SOFT_VOL_MSG(me_ptr->miid, DBG_HIGH_PRIO, "Gain 0x%x sent to control port 0x%x", gain_payload->gain_data[0].gain, control_port_id); + } + + // Get the next control port id for the intent #INTENT_ID_GAIN_INFO + capi_cmn_ctrl_port_list_get_next_port_data(&me_ptr->ctrl_port_info, + INTENT_ID_GAIN_INFO, + control_port_id, // initially, an invalid port id + &port_data_ptr); + + if (port_data_ptr) + { + control_port_id = port_data_ptr->port_info.port_id; + port_state = port_data_ptr->state; + } + } +} + +#ifdef DO_SOFT_VOL_PROFILING +void capi_soft_vol_print_kpps(capi_soft_vol_t *me_ptr) +{ + soft_vol_prof_info_t *soft_vol_kpps_data = &me_ptr->soft_vol_kpps_data; + + soft_vol_kpps_data->average_kpps = + ((soft_vol_kpps_data->total_cycles / soft_vol_kpps_data->sample_count) * soft_vol_kpps_data->sample_rate) / + (1000); + + SOFT_VOL_MSG(me_ptr->miid, + DBG_HIGH_PRIO, + "Total cycles :%llu, Total KPPS: %llu, " + "Total Sample Count: %llu, Peak KPPS : %llu", + soft_vol_kpps_data->total_cycles, + soft_vol_kpps_data->average_kpps, + soft_vol_kpps_data->sample_count, + soft_vol_kpps_data->peak_kpps); + + soft_vol_kpps_data->total_cycles = 0; + soft_vol_kpps_data->peak_kpps = 0; + soft_vol_kpps_data->sample_count = 0; + soft_vol_kpps_data->average_kpps = 0; +} + +void capi_soft_vol_profiling(capi_soft_vol_t *me_ptr, uint32_t num_samples) +{ + soft_vol_prof_info_t *soft_vol_kpps_data = &me_ptr->soft_vol_kpps_data; + + soft_vol_kpps_data->total_cycles = + soft_vol_kpps_data->total_cycles + (soft_vol_kpps_data->end_cycles - soft_vol_kpps_data->start_cycles); + + soft_vol_kpps_data->sample_count += num_samples; + + uint64_t frame_kpps = soft_vol_kpps_data->end_cycles - soft_vol_kpps_data->start_cycles; + frame_kpps = frame_kpps / num_samples; + frame_kpps = frame_kpps * soft_vol_kpps_data->sample_rate / 1000; + + if (frame_kpps > soft_vol_kpps_data->peak_kpps) + { + soft_vol_kpps_data->peak_kpps = frame_kpps; + } +} +#endif /* DO_SOFT_VOL_PROFILING */ diff --git a/modules/processing/volume_control/capi/soft_vol/src/capi_soft_vol_utils.h b/modules/processing/volume_control/capi/soft_vol/src/capi_soft_vol_utils.h new file mode 100644 index 0000000..8ab03d2 --- /dev/null +++ b/modules/processing/volume_control/capi/soft_vol/src/capi_soft_vol_utils.h @@ -0,0 +1,296 @@ +#ifndef CAPI_SOFT_VOL_UTILS_H +#define CAPI_SOFT_VOL_UTILS_H +/* ========================================================================= + * Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + * SPDX-License-Identifier: BSD-3-Clause-Clear + * =========================================================================*/ + +/** + * @file capi_soft_vol_utils.h + * + * Utility header for soft_vol module. + */ + +/*===================================================================== + Includes + ======================================================================*/ +#include "capi.h" +#include "SoftVolumeControls.h" +#include "soft_vol_api.h" +#include "posal.h" +#include "capi_cmn.h" + +//For imcl to AVC Tx in voice path +#include "capi_cmn_ctrl_port_list.h" +#include "imcl_module_gain_api.h" +#include "imcl_p_eq_vol_api.h" +#include "imcl_mute_api.h" + +#define CHECK_THROW_ERROR(result, error_msg, ...) \ + { \ + if (CAPI_FAILED(result)) \ + { \ + AR_MSG(DBG_ERROR_PRIO, error_msg, ##__VA_ARGS__); \ + return result; \ + } \ + \ +} + +/*===================================================================== + Macros + ======================================================================*/ +#define MAX_PERIOD 15000 +#define MIN_PERIOD 0 +#define MAX_STEP 15000000 +#define MIN_STEP 0 +#define MULT_CH_GAIN_BASE_PYLD_SIZE sizeof(volume_ctrl_channels_gain_config_v2_t) +#define MULT_CH_MUTE_BASE_PYLD_SIZE sizeof(volume_ctrl_channels_mute_config_v2_t) + +#ifdef PROD_SPECIFIC_MAX_CH +#define VOLUME_CONTROL_MAX_CHANNELS_V2 128 +#else +#define VOLUME_CONTROL_MAX_CHANNELS_V2 32 +#endif + +#define HEADROOM_GAIN_Q28_MIN 4254 +#define HEADROOM_GAIN_Q28_MAX 268435456 + +#define SOFT_VOL_MASK_WIDTH 32 +#define SOFT_VOL_MAX_CHANNEL_TYPE PCM_MAX_CHANNEL_MAP_V2 + +const uint32_t UNITY_GAIN_Q28 = 1 << 28; +const uint16_t UNITY_GAIN_Q13 = 1 << 13; + +const uint16_t MIN_CHANNEL_TYPE = PCM_CHANNEL_L; +const uint16_t MAX_CHANNEL_TYPE = PCM_MAX_CHANNEL_MAP_V2; +#define SOFT_VOL_DEBUG 0 +/* debug message */ +#define MIID_UNKNOWN 0 +#define SOFT_VOL_MSG_PREFIX "CAPI SOFT_VOL:[%lX] " +#define SOFT_VOL_MSG(ID, xx_ss_mask, xx_fmt, ...)\ + AR_MSG(xx_ss_mask, SOFT_VOL_MSG_PREFIX xx_fmt, ID, ##__VA_ARGS__) + +#ifdef __qdsp6__ +#define s64_mult_fp_s32_s16_shift(var1, var2, shift) Q6_P_asl_PR(Q6_P_mpy_RR(var1, (int32)var2), shift - 16) +#else +#define s64_mult_fp_s32_s16_shift(var1, var2, shift) s64_mult_s32_s16_shift(var1, var2, shift) +#endif + +#define CAPI_SOFT_VOL_ALIGN_4_BYTE(x) (((x) + 3) & (0xFFFFFFFC)) + +/*===================================================================== + Structure definitions + ======================================================================*/ +typedef enum { + SOFT_VOL_MIN_IN_CHANNELS = 1, + SOFT_VOL_MIN_OUT_CHANNELS = 1, +#ifdef PROD_SPECIFIC_MAX_CH + SOFT_VOL_MAX_IN_CHANNELS = PROD_SPECIFIC_MAX_CH, + SOFT_VOL_MAX_OUT_CHANNELS = PROD_SPECIFIC_MAX_CH, +#else + SOFT_VOL_MAX_IN_CHANNELS = 32, + SOFT_VOL_MAX_OUT_CHANNELS = 32, +#endif +} soft_vol_ch_t; + +typedef enum { SOFT_VOL_WAITING_FOR_MEDIA_FORMAT = -1, SOFT_VOL_DISABLE, SOFT_VOL_ENABLE } soft_vol_state_t; + +#ifdef DO_SOFT_VOL_PROFILING +typedef struct soft_vol_prof_info +{ + uint32_t sample_rate; + uint64_t start_cycles; + uint64_t end_cycles; + uint64_t total_cycles; + uint64_t sample_count; + uint64_t average_kpps; + uint64_t peak_kpps; +} soft_vol_prof_info_t; +#endif + +#define CAPI_SOFT_VOL_MF_V2_MIN_SIZE \ + (sizeof(capi_standard_data_format_v2_t) + sizeof(capi_set_get_media_format_t)) + +typedef struct capi_soft_vol_power_info +{ + uint32_t kpps; + uint32_t code_bw; + uint32_t data_bw; +} capi_soft_vol_power_info_t; + +typedef struct capi_soft_vol_multich_gain_info +{ + uint32_t gain; + uint32_t group_ch_mask; + uint32_t multch_gain_mask_list[CAPI_CMN_MAX_CHANNEL_MAP_GROUPS]; +} capi_soft_vol_multich_gain_info; + +typedef struct soft_vol_lib_struct +{ + uint16_t masterGain; + + /* +1 to be able to index the array directly by channel type. */ + uint32_t channelGain[MAX_CHANNEL_TYPE + 1]; + void * pPerChannelData[MAX_CHANNEL_TYPE + 1]; + + int32_t headroom_mB; + int32_t topoReqHeadroom; + uint32_t numChannels; + uint16_t channelMapping[VOLUME_CONTROL_MAX_CHANNELS_V2]; + + bool_t softPauseEnable; +} soft_vol_lib_struct_t; + +typedef struct capi_soft_vol +{ + const capi_vtbl_t * vtbl_ptr; + uint32_t heap_id; + capi_event_callback_info_t cb_info; + capi_media_fmt_v2_t input_media_fmt; + capi_media_fmt_v2_t output_media_fmt; + capi_soft_vol_power_info_t power_info; + soft_vol_lib_struct_t soft_vol_lib; + CSoftVolumeControlsLib SoftVolumeControlsLib; + soft_vol_state_t soft_vol_state; + + //for imcl to AVC-Tx + ctrl_port_list_handle_t ctrl_port_info; + bool_t update_gain_over_imcl; + int8_t adjust_volume_based_on_headroom; +#ifdef DO_SOFT_VOL_PROFILING + soft_vol_prof_info_t soft_vol_kpps_data; +#endif + uint32_t miid; + //this flag is to detect if greater than 63 channels received in media format + bool_t higher_channel_map_present; +} capi_soft_vol_t; + +capi_err_t capi_vol_imc_set_param_handler(capi_soft_vol *me_ptr, capi_buf_t *intent_buf_ptr); + +capi_err_t capi_set_default_ramp_params(capi_soft_vol_t *me_ptr); + +capi_err_t capi_soft_vol_process_set_properties(capi_soft_vol_t *me_ptr, capi_proplist_t *proplist_ptr); + +capi_err_t capi_soft_vol_process_get_properties(capi_soft_vol_t *me_ptr, capi_proplist_t *proplist_ptr); + +void capi_soft_vol_profiling(capi_soft_vol_t *me_ptr, uint32_t num_samples); + +void capi_set_soft_stepping_param(const capi_soft_vol_t *me_ptr, + SoftSteppingParams * pParams, + uint32_t period, + uint32_t step, + uint32_t rampingCurve); + +void capi_get_soft_stepping_param(const capi_soft_vol_t *me_ptr, + const SoftSteppingParams *pParams, + uint32_t * pPeriod, + uint32_t * pStep, + uint32_t * pRampingCurve); + +bool_t check_channel_mask_softvol(uint8_t *iir_param_ptr,uint32_t param_id) ; + +capi_err_t capi_soft_vol_set_multichannel_gain(capi_soft_vol_t * me_ptr, + volume_ctrl_multichannel_gain_t *pVolumePkt); + +bool_t check_unique_gain(uint32_t *gain_list, uint32_t gain, uint32_t *num_unique_gains_ptr, uint32_t *position_ptr); + +uint32_t capi_soft_vol_get_multichannel_gain(capi_soft_vol_t *me_ptr, volume_ctrl_multichannel_gain_t *pVolumePkt); + +capi_err_t capi_soft_vol_set_multichannel_mute(capi_soft_vol_t * me_ptr, + volume_ctrl_multichannel_mute_t *pMutePkt); + +void capi_soft_vol_get_multichannel_mute(capi_soft_vol_t *me_ptr, volume_ctrl_multichannel_mute_t *pMutePkt); + +void capi_soft_vol_set_mute_for_all_channels(capi_soft_vol_t *me_ptr, const uint32_t mute_flag); + +void capi_soft_vol_reinit_new_channels(capi_soft_vol_t *me_ptr, + const uint32_t numNewChannels, + const uint8_t newMapping[]); + +uint32 capi_soft_vol_calc_gain_q28(const uint16_t masterGainQ13,const uint32_t channelGainQ28); + +void capi_soft_vol_set_sample_rate(capi_soft_vol_t *me_ptr, const uint32_t sample_rate); + +int32 capi_soft_vol_convert_headroom_linear_gain(const int32 reqHeadroom); + +capi_err_t capi_soft_vol_headroom_gain(capi_soft_vol_t *me_ptr, const int32_t headroom_mB); + +capi_err_t capi_soft_vol_init_lib_memory(capi_soft_vol_t *me_ptr); + +void capi_soft_vol_send_gain_over_imcl(capi_soft_vol_t *me_ptr); + +capi_err_t capi_soft_vol_validate_multichannel_payload(capi_soft_vol_t *me_ptr, + uint32_t num_cfg, + int8_t *payload_cfg_ptr, + uint32_t param_size, + uint32_t *required_size_ptr, + uint32_t param_id); + +capi_err_t capi_soft_vol_set_multichannel_gain_v2(capi_soft_vol_t *me_ptr, + volume_ctrl_multichannel_gain_v2_t *vol_payload_ptr); + +capi_err_t capi_soft_vol_set_multichannel_mute_v2(capi_soft_vol_t *me_ptr, + volume_ctrl_multichannel_mute_v2_t *mute_payload_ptr); + +void capi_soft_vol_get_multichannel_mute_v2(capi_soft_vol_t *me_ptr, + int8_t *get_payload_ptr, + uint32_t *mute_chamask_list_ptr); + +uint32_t capi_soft_vol_get_multichannel_gain_v2_payload_size(capi_soft_vol_t *me_ptr, + capi_soft_vol_multich_gain_info *gain_list, + uint32_t *req_size_ptr ); + +capi_err_t capi_soft_vol_get_multichannel_gain_v2(capi_soft_vol_t *me_ptr, + int8_t *get_payload_ptr, + capi_soft_vol_multich_gain_info *gain_list, + uint32_t payload_size, + uint32_t num_unique_gains); + +void capi_soft_vol_update_mult_ch_gain_info(capi_soft_vol_t * me_ptr, + capi_soft_vol_multich_gain_info *gain_list, + uint32_t gain, + uint32_t *num_unique_gains_ptr, + uint32_t *position_ptr, + uint32_t channel_type, + uint32_t *req_size_ptr); + +void capi_soft_vol_update_gain_ch_mask_list(capi_soft_vol_t * me_ptr, + capi_soft_vol_multich_gain_info *gain_list, + uint32_t *position_ptr, + uint32_t channel_type, + uint32_t *req_size_ptr); + +capi_err_t capi_soft_vol_get_multichannel_mute_v2_payload_size(capi_soft_vol_t *me_ptr, + uint32_t *mute_chmask_list, + uint32_t *req_size_ptr ); + +bool_t capi_soft_vol_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_soft_vol_process(capi_t * _pif, + capi_stream_data_t *input[], + capi_stream_data_t *output[]); + +capi_err_t capi_soft_vol_end(capi_t *_pif); + +capi_err_t capi_soft_vol_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 capi_soft_vol_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 capi_soft_vol_set_properties(capi_t *_pif, capi_proplist_t *props_ptr); + +capi_err_t capi_soft_vol_get_properties(capi_t *_pif, capi_proplist_t *props_ptr); + +capi_vtbl_t *capi_soft_vol_get_vtbl(); + +#endif /* CAPI_SOFT_VOL_UTILS_H */ diff --git a/modules/processing/volume_control/capi/soft_vol/src/capi_soft_vol_utils_v2.cpp b/modules/processing/volume_control/capi/soft_vol/src/capi_soft_vol_utils_v2.cpp new file mode 100644 index 0000000..ff8e835 --- /dev/null +++ b/modules/processing/volume_control/capi/soft_vol/src/capi_soft_vol_utils_v2.cpp @@ -0,0 +1,535 @@ +/* ========================================================================= + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause-Clear + * =========================================================================*/ + +/** + * @file capi_soft_vol_utils_v2.cpp + * + * Utility functions for soft_vol module. + */ + +#include "ar_defs.h" +#include "audpp_util.h" +#include "capi_soft_vol_utils.h" +#include "audio_basic_op_ext.h" +#include "audio_exp10.h" + +capi_err_t capi_soft_vol_validate_multichannel_payload(capi_soft_vol_t *me_ptr, + uint32_t num_cfg, + int8_t * payload_cfg_ptr, + uint32_t param_size, + uint32_t * required_size_ptr, + uint32_t param_id) +{ + capi_err_t capi_result = CAPI_EOK; + int8_t * base_payload_ptr = payload_cfg_ptr; + uint32_t per_cfg_base_payload_size = 0; + uint32_t config_size = 0; + switch (param_id) + { + case PARAM_ID_VOL_CTRL_MULTICHANNEL_GAIN_V2: { + *required_size_ptr += sizeof(volume_ctrl_multichannel_gain_v2_t); + per_cfg_base_payload_size = MULT_CH_GAIN_BASE_PYLD_SIZE; + break; + } + case PARAM_ID_VOL_CTRL_MULTICHANNEL_MUTE_V2: { + *required_size_ptr += sizeof(volume_ctrl_multichannel_mute_v2_t); + per_cfg_base_payload_size = MULT_CH_MUTE_BASE_PYLD_SIZE; + } + } + base_payload_ptr += *required_size_ptr; // 4 + // configuration payload + // _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + //| num_ | channel_type_group_mask | dynamic channel_type_mask_list[0] | payload1(mute/gain) |_ _ _ + //|config| _ _ _ _ _ _ _ _ _ _ _ _ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |_ _ _ _ _ _ _ _ _ _ | + // +#ifdef SOFT_VOL_DEBUG + SOFT_VOL_MSG(me_ptr->miid, DBG_LOW_PRIO, "Validating received payload for %#x.", param_id); +#endif + for (uint32_t count = 0; count < num_cfg; count++) + { + if ((*required_size_ptr + per_cfg_base_payload_size) > param_size) + { + SOFT_VOL_MSG(me_ptr->miid, + DBG_ERROR_PRIO, + "Failed while validating required size for config #%lu. Total config: %lu", + count + 1, + num_cfg); + return CAPI_ENEEDMORE; + } + else + { + uint32_t ch_type_group_mask = 0; + base_payload_ptr += config_size; + ch_type_group_mask = *((uint32_t *)base_payload_ptr); + +#ifdef SOFT_VOL_DEBUG + SOFT_VOL_MSG(me_ptr->miid, + DBG_MED_PRIO, + "channel group mask %lu received for config %lu.", + ch_type_group_mask, + count + 1); +#endif + // check for group mask payload if only desired bits are set or not + if (0 == (ch_type_group_mask >> CAPI_CMN_MAX_CHANNEL_MAP_GROUPS)) + { + capi_result = capi_cmn_check_payload_validation(me_ptr->miid, + ch_type_group_mask, + per_cfg_base_payload_size, + count + 1, + param_size, + &config_size, + required_size_ptr); + if(CAPI_FAILED(capi_result)) + { + return capi_result; + } + } + else + { + SOFT_VOL_MSG(me_ptr->miid, + DBG_ERROR_PRIO, + "Invalid configuration of channel group mask: %lu. More than maximum valid channel groups are " + "set, maximum valid channel groups: %lu.", + ch_type_group_mask, + CAPI_CMN_MAX_CHANNEL_MAP_GROUPS); + return CAPI_EBADPARAM; + } + } + } +#ifdef SOFT_VOL_DEBUG + SOFT_VOL_MSG(me_ptr->miid, DBG_MED_PRIO, "Total calculated size for param is %lu", *required_size_ptr); +#endif + + // validate the configs for duplication + if (!capi_soft_vol_check_multi_ch_channel_mask_v2_param(me_ptr->miid, + num_cfg, + param_id, + (int8_t *)payload_cfg_ptr, + sizeof(volume_ctrl_multichannel_gain_v2_t), + per_cfg_base_payload_size)) + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, "Trying to set different gain values to same channel, returning."); + return CAPI_EBADPARAM; + } + return CAPI_EOK; +} +/* Returns FALSE if same channel is set to multiple times in different configs */ +bool_t capi_soft_vol_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) +{ + uint32_t *temp_mask_list_ptr = NULL; + int8_t * data_ptr = param_ptr; // points to the start of the payload + uint32_t check_channel_mask_arr[CAPI_CMN_MAX_CHANNEL_MAP_GROUPS] = { 0 }; + uint32_t current_channel_mask_arr[CAPI_CMN_MAX_CHANNEL_MAP_GROUPS] = { 0 }; + bool_t check = TRUE; + uint32_t offset = base_payload_size; + + for (uint32_t cnfg_cntr = 0; cnfg_cntr < num_config; cnfg_cntr++) + { + uint32_t channel_group_mask = 0; + // Increment data ptr by calculated offset to point at next payload's group_mask + data_ptr += offset; + channel_group_mask = *((uint32_t *)data_ptr); +#ifdef SOFT_VOL_DEBUG + SOFT_VOL_MSG(miid, + DBG_MED_PRIO, + "channel group mask %#lx received for config %lu.", + channel_group_mask, + cnfg_cntr + 1); +#endif + temp_mask_list_ptr = (uint32_t *)(data_ptr + CAPI_CMN_INT32_SIZE_IN_BYTES); + if(!capi_cmn_check_v2_channel_mask_duplication(miid, + cnfg_cntr, + channel_group_mask, + temp_mask_list_ptr, + current_channel_mask_arr, + check_channel_mask_arr, + &offset, + per_cfg_base_payload_size)) + { + return FALSE; + } + } + return check; +} + +capi_err_t capi_soft_vol_set_multichannel_gain_v2(capi_soft_vol_t * me_ptr, + volume_ctrl_multichannel_gain_v2_t *vol_payload_ptr) +{ + + uint32_t *temp_cfg_ptr = NULL; + uint32_t *gain_ref_ptr = NULL; + int8_t * data_ptr = (int8_t *)vol_payload_ptr; + uint32_t offset = sizeof(volume_ctrl_multichannel_gain_v2_t); + + // loop through all the configs + for (uint32_t cnfg_cntr = 0; cnfg_cntr < vol_payload_ptr->num_config; cnfg_cntr++) + { + uint32_t ch_mask_arr_index = 0; + uint32_t channel_group_mask = 0; + uint32_t ch_mask_list_size = 0; + + // Increment data ptr by calculated offset to point at next payload's group_mask + data_ptr += offset; + volume_ctrl_channels_gain_config_v2_t *temp_multich_gain_cfg = (volume_ctrl_channels_gain_config_v2_t *)data_ptr; + channel_group_mask = temp_multich_gain_cfg->channel_type_group_mask; + temp_cfg_ptr = (uint32_t *)(data_ptr + CAPI_CMN_INT32_SIZE_IN_BYTES); // points to mask array + ch_mask_list_size = capi_cmn_count_set_bits(channel_group_mask); + gain_ref_ptr = temp_cfg_ptr + ch_mask_list_size; // points to gain payload +#ifdef SOFT_VOL_DEBUG + SOFT_VOL_MSG(me_ptr->miid, DBG_HIGH_PRIO, "Gain for config #%lu is %#lx", cnfg_cntr + 1, *gain_ref_ptr); +#endif + // setting the gain data channel wise + for (uint32_t group_no = 0; group_no < CAPI_CMN_MAX_CHANNEL_MAP_GROUPS; group_no++) + { + // check if a group is configured. If yes, update ch_index_mask_to_log_ptr + if (CAPI_CMN_IS_BIT_SET_AT_POS_IN_32B_VAL(channel_group_mask, group_no)) + { + uint32_t start_ch_map = group_no * CAPI_CMN_CHANNELS_PER_MASK; + uint32_t end_ch_map = (group_no + 1) * CAPI_CMN_CHANNELS_PER_MASK; + uint32_t j = 0; + if(0 == group_no) + { + start_ch_map = 1; //ignoring reserved bit 0 + j = 1; + } + if(PCM_MAX_CHANNEL_MAP_V2 < end_ch_map) + { + end_ch_map = PCM_MAX_CHANNEL_MAP_V2 + 1; + } + for (uint32_t i = start_ch_map; i < end_ch_map; i++, j++) + { + uint32_t ch_mask = (1 << j); + if (ch_mask & temp_cfg_ptr[ch_mask_arr_index]) // check if this channel is set anywhere in this group + { + me_ptr->soft_vol_lib.channelGain[i] = *gain_ref_ptr; // copy the respective payload + uint32 gainQ28 = + capi_soft_vol_calc_gain_q28(me_ptr->soft_vol_lib.masterGain, me_ptr->soft_vol_lib.channelGain[i]); + me_ptr->SoftVolumeControlsLib.SetVolume(gainQ28, me_ptr->soft_vol_lib.pPerChannelData[i]); + } + } +#ifdef SOFT_VOL_DEBUG + SOFT_VOL_MSG(me_ptr->miid, DBG_HIGH_PRIO, + "Set volume ctrl: config: #%lu, channel mask: %#lx, group no: %lu," + "includes channels from %lu to %lu. Gain: %#lx", + cnfg_cntr + 1, + temp_cfg_ptr[ch_mask_arr_index], + group_no, + start_ch_map, + end_ch_map - 1, + *gain_ref_ptr); +#endif + ch_mask_arr_index++; + } + } + offset = sizeof(volume_ctrl_channels_gain_config_v2_t) + (ch_mask_arr_index << 2); //sizeof(uint32_t) + } + return CAPI_EOK; +} + +capi_err_t capi_soft_vol_set_multichannel_mute_v2(capi_soft_vol_t * me_ptr, + volume_ctrl_multichannel_mute_v2_t *mute_payload_ptr) +{ + uint32_t *temp_cfg_ptr = NULL; + uint32_t *mute_ref_ptr = NULL; + int8_t * data_ptr = (int8_t *)mute_payload_ptr; + uint32_t offset = sizeof(volume_ctrl_multichannel_mute_v2_t); + + // loop through all the configs + for (uint32_t cnfg_cntr = 0; cnfg_cntr < mute_payload_ptr->num_config; cnfg_cntr++) + { + uint32_t ch_mask_arr_index = 0; + uint32_t channel_group_mask = 0; + uint32_t ch_mask_list_size = 0; + + // Increment data ptr by calculated offset to point at next payload's group_mask + data_ptr += offset; + volume_ctrl_channels_mute_config_v2_t *temp_multich_mute_cfg = (volume_ctrl_channels_mute_config_v2_t *)data_ptr; + channel_group_mask = temp_multich_mute_cfg->channel_type_group_mask; + + temp_cfg_ptr = (uint32_t *)(data_ptr + CAPI_CMN_INT32_SIZE_IN_BYTES); // points to the mask array + ch_mask_list_size = capi_cmn_count_set_bits(channel_group_mask); + mute_ref_ptr = temp_cfg_ptr + ch_mask_list_size; // points to the mute payload + + for (uint32_t group_no = 0; group_no < CAPI_CMN_MAX_CHANNEL_MAP_GROUPS; group_no++) + { + // check if a group is configured. + if (CAPI_CMN_IS_BIT_SET_AT_POS_IN_32B_VAL(channel_group_mask, group_no)) + { + uint32_t start_ch_map = group_no * CAPI_CMN_CHANNELS_PER_MASK; + uint32_t end_ch_map = (group_no + 1) * CAPI_CMN_CHANNELS_PER_MASK; + uint32_t j = 0; + if(0 == group_no) + { + start_ch_map = 1; //ignoring reserved bit 0 + j = 1; + } + if(PCM_MAX_CHANNEL_MAP_V2 < end_ch_map) + { + end_ch_map = PCM_MAX_CHANNEL_MAP_V2 + 1; + } + // iterate over the set of channel maps for this group + for (uint32_t i = start_ch_map; i < end_ch_map; i++, j++) + { + uint32_t ch_mask = (1 << j); + if (ch_mask & temp_cfg_ptr[ch_mask_arr_index]) // check if this channel is set anywhere in this group + { + if (0 == *mute_ref_ptr) + { + me_ptr->SoftVolumeControlsLib.Unmute(me_ptr->soft_vol_lib.pPerChannelData[i]); + } + else + { + me_ptr->SoftVolumeControlsLib.Mute(me_ptr->soft_vol_lib.pPerChannelData[i]); + } + +#ifdef SOFT_VOL_DEBUG + SOFT_VOL_MSG(me_ptr->miid, DBG_HIGH_PRIO, "Set volume ctrl: channel: %hu Mute: 0x%lx", i, *mute_ref_ptr); +#endif + } + } +#ifdef SOFT_VOL_DEBUG + SOFT_VOL_MSG(me_ptr->miid, DBG_HIGH_PRIO, + "Set volume ctrl: config: #%lu, channel mask: %#lx, group no: %lu," + "includes channels from %lu to %lu. Gain: %#lx", + cnfg_cntr + 1, + temp_cfg_ptr[ch_mask_arr_index], + group_no, + start_ch_map, + end_ch_map - 1, + *mute_ref_ptr); +#endif + ch_mask_arr_index++; + } + } + offset = sizeof(volume_ctrl_channels_mute_config_v2_t) + (ch_mask_arr_index << 2); //sizeof(uint32_t) + } + return CAPI_EOK; +} + +capi_err_t capi_soft_vol_get_multichannel_mute_v2_payload_size(capi_soft_vol_t *me_ptr, + uint32_t * mute_chmask_list, + uint32_t * req_size_ptr) +{ + uint32_t mute = 0; + uint32_t channel_group_mask = 0; + uint16_t channel_type = 0; + + for (int i = 0; i < me_ptr->soft_vol_lib.numChannels; i++) + { + channel_type = me_ptr->soft_vol_lib.channelMapping[i]; + mute = (me_ptr->SoftVolumeControlsLib.IsMuted( + me_ptr->soft_vol_lib.pPerChannelData[me_ptr->soft_vol_lib.channelMapping[i]])) + ? 1 + : 0; + if (mute == 1) + { + // get the group index in which this particular chan type should be stored + uint32_t group_index = CAPI_CMN_DIVIDE_WITH_32(channel_type); + // get the bit position in which this particular chan type should be stored + // in mute_chmask_list[group_index] + uint32_t ch_bit_position = CAPI_CMN_MOD_WITH_32(channel_type); + // add this channel type to mask list + mute_chmask_list[group_index] |= CAPI_CMN_CONVERT_TO_32B_MASK(ch_bit_position); + } + } + //check for the set groups, then update ch group mask and required size for group mask + for (uint32_t group_index = 0; group_index < CAPI_CMN_MAX_CHANNEL_MAP_GROUPS; group_index++) + { + if (mute_chmask_list[group_index]) + { + channel_group_mask |= CAPI_CMN_CONVERT_TO_32B_MASK(group_index); + *req_size_ptr += CAPI_CMN_INT32_SIZE_IN_BYTES; + } + } + if (channel_group_mask) // if there is a valid group mask (>0), update the required size + { + *req_size_ptr += (sizeof(volume_ctrl_multichannel_mute_v2_t) + sizeof(volume_ctrl_channels_mute_config_v2_t)); + } + else + { + SOFT_VOL_MSG(me_ptr->miid, DBG_ERROR_PRIO, "No mute config present! Returning."); + return CAPI_EBADPARAM; + } + return CAPI_EOK; +} + +void capi_soft_vol_get_multichannel_mute_v2(capi_soft_vol_t *me_ptr, + int8_t * get_payload_ptr, + uint32_t * mute_chamask_list_ptr) +{ + uint32_t *temp_payload_ptr = NULL; + uint32_t mute = 1; + uint32_t channel_group_mask = 0; + temp_payload_ptr = (uint32_t *)get_payload_ptr; + + // group mask creation + for (uint32_t group_index = 0; group_index < CAPI_CMN_MAX_CHANNEL_MAP_GROUPS; group_index++) + { + if (mute_chamask_list_ptr[group_index]) + { + channel_group_mask |= CAPI_CMN_CONVERT_TO_32B_MASK(group_index); + } + } + if (channel_group_mask) + { + uint32_t chmask_arr_index = 0; + *temp_payload_ptr = 1; // num_config is 1 (only mute config) + temp_payload_ptr++; + *temp_payload_ptr = channel_group_mask; + temp_payload_ptr++; + for (uint32_t group_index = 0; group_index < CAPI_CMN_MAX_CHANNEL_MAP_GROUPS; group_index++) + { + if (mute_chamask_list_ptr[group_index]) + { + temp_payload_ptr[chmask_arr_index] = mute_chamask_list_ptr[group_index]; + chmask_arr_index++; + } + } + temp_payload_ptr += chmask_arr_index; // pointing to the mute payload + *temp_payload_ptr = mute; // mute + } +} + +uint32_t capi_soft_vol_get_multichannel_gain_v2_payload_size(capi_soft_vol_t * me_ptr, + capi_soft_vol_multich_gain_info *gain_list, + uint32_t * req_size_ptr) +{ + uint32_t position = 0; + uint32_t num_unique_gains = 0; // num_unique_gains is equivalent to num_configs + uint32_t gain = 0; + uint16_t channel_type = 0; + + *req_size_ptr += sizeof(volume_ctrl_multichannel_gain_v2_t); + + for (uint32_t i = 0; i < me_ptr->soft_vol_lib.numChannels; i++) + { + gain = me_ptr->soft_vol_lib.channelGain[me_ptr->soft_vol_lib.channelMapping[i]]; + channel_type = me_ptr->soft_vol_lib.channelMapping[i]; + + capi_soft_vol_update_mult_ch_gain_info(me_ptr, gain_list, gain, &num_unique_gains, &position, channel_type, req_size_ptr); + } +#ifdef SOFT_VOL_DEBUG + SOFT_VOL_MSG(me_ptr->miid, DBG_LOW_PRIO, "Get multichannel volume, num_unique_gains(num_cfg): %lu" + ". Required size of payload: %lu", + num_unique_gains, + *req_size_ptr); +#endif + return num_unique_gains; +} + +/* checks if the gain is already present in the previous configs added and updates the number of unique gains + * (equivalent to number of configs), updates the corresponding channel mask info*/ +void capi_soft_vol_update_mult_ch_gain_info(capi_soft_vol_t * me_ptr, + capi_soft_vol_multich_gain_info *gain_list, + uint32_t gain, + uint32_t * num_unique_gains_ptr, + uint32_t * position_ptr, + uint32_t channel_type, + uint32_t * req_size_ptr) +{ + bool_t unique = TRUE; + // loop through all the unique gains(num_cfg till now) in gain_list, check if gain is not unique, + // then update the gain_list without incrementing position. + for (uint32_t i = 0; i < *num_unique_gains_ptr; i++) + { + if (gain == gain_list[i].gain) + { + unique = FALSE; + *position_ptr = i; + capi_soft_vol_update_gain_ch_mask_list(me_ptr, &gain_list[0], position_ptr, channel_type, req_size_ptr); + break; + } + } + //if gain is unique, then add the gain to gain list, increment the req size with per cfg size and + //update the gain_list with incrementing position. + if (unique) + { + gain_list[*num_unique_gains_ptr].gain = gain; + *req_size_ptr += MULT_CH_GAIN_BASE_PYLD_SIZE; // group_mask + gain = 8B + *position_ptr = *num_unique_gains_ptr; + capi_soft_vol_update_gain_ch_mask_list(me_ptr, &gain_list[0], position_ptr, channel_type, req_size_ptr); + *num_unique_gains_ptr += 1; + } +} +void capi_soft_vol_update_gain_ch_mask_list(capi_soft_vol_t * me_ptr, + capi_soft_vol_multich_gain_info *gain_list, + uint32_t * position_ptr, + uint32_t channel_type, + uint32_t * req_size_ptr) +{ + // get the group index in which this particular ch should be stored + uint32_t group_index = CAPI_CMN_DIVIDE_WITH_32(channel_type); + if (!CAPI_CMN_IS_BIT_SET_AT_POS_IN_32B_VAL(gain_list[*position_ptr].group_ch_mask, group_index)) + { + gain_list[*position_ptr].group_ch_mask |= CAPI_CMN_CONVERT_TO_32B_MASK(group_index); + *req_size_ptr += CAPI_CMN_INT32_SIZE_IN_BYTES; // size of channel mask list size would be increased by 1 +#ifdef SOFT_VOL_DEBUG + SOFT_VOL_MSG(me_ptr->miid, + DBG_LOW_PRIO, + "gain_list[%lu].group_ch_mask = %#lx", + *position_ptr, + gain_list[*position_ptr].group_ch_mask); +#endif + } + // get the bit position in which this particular channel should be stored + // in channel mask list + uint32_t ch_bit_position = CAPI_CMN_MOD_WITH_32(channel_type); + // update this channel_type in the list + gain_list[*position_ptr].multch_gain_mask_list[group_index] |= CAPI_CMN_CONVERT_TO_32B_MASK(ch_bit_position); +#ifdef SOFT_VOL_DEBUG + SOFT_VOL_MSG(me_ptr->miid, + DBG_LOW_PRIO, + "gain_list[%lu].multch_gain_mask_list[%lu] = %#lx", + *position_ptr, + group_index, + gain_list[*position_ptr].multch_gain_mask_list[group_index]); +#endif +} + +capi_err_t capi_soft_vol_get_multichannel_gain_v2(capi_soft_vol_t * me_ptr, + int8_t * get_payload_ptr, + capi_soft_vol_multich_gain_info *gain_list, + uint32_t payload_size, + uint32_t num_unique_gains) +{ + uint32_t dest_size = 0; + dest_size += sizeof(volume_ctrl_multichannel_gain_v2_t); + uint32 *base_payload_ptr = (uint32_t *)get_payload_ptr; + *base_payload_ptr = num_unique_gains; // copying the num_config + + for (uint32_t i = 0; i < num_unique_gains; i++) + { + base_payload_ptr++; + *base_payload_ptr = gain_list[i].group_ch_mask; + dest_size += CAPI_CMN_INT32_SIZE_IN_BYTES; // group mask + base_payload_ptr++; + for (uint32_t j = 0; j < CAPI_CMN_MAX_CHANNEL_MAP_GROUPS; j++) + { + if (gain_list[i].multch_gain_mask_list[j]) + { + *base_payload_ptr = gain_list[i].multch_gain_mask_list[j]; + dest_size += CAPI_CMN_INT32_SIZE_IN_BYTES; + base_payload_ptr++; + } + } + *base_payload_ptr = gain_list[i].gain; + dest_size += CAPI_CMN_INT32_SIZE_IN_BYTES; // gain + } + if (payload_size != dest_size) + { + SOFT_VOL_MSG(me_ptr->miid, + DBG_ERROR_PRIO, + "Get multichannel volume, Bad param size %lu. Required param_size %lu", + dest_size, + payload_size); + return CAPI_EFAILED; + } + return CAPI_EOK; +} diff --git a/modules/processing/volume_control/capi/soft_vol/stub_src/capi_soft_vol_stub.cpp b/modules/processing/volume_control/capi/soft_vol/stub_src/capi_soft_vol_stub.cpp new file mode 100644 index 0000000..862d9c6 --- /dev/null +++ b/modules/processing/volume_control/capi/soft_vol/stub_src/capi_soft_vol_stub.cpp @@ -0,0 +1,28 @@ +/* ========================================================================= + Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + SPDX-License-Identifier: BSD-3-Clause-Clear + * =========================================================================*/ + +/** + * @file capi_soft_vol_stub.cpp + * + * Stub Interface for soft_vol module. + */ + +#include "capi_soft_vol.h" + +capi_err_t capi_soft_vol_get_static_properties( + capi_proplist_t *init_set_properties, + capi_proplist_t *static_properties) +{ + return CAPI_EUNSUPPORTED; +} + + +capi_err_t capi_soft_vol_init( + capi_t *_pif, + capi_proplist_t *init_set_properties) +{ + return CAPI_EUNSUPPORTED; +} + diff --git a/modules/processing/volume_control/lib/inc/SoftVolumeControls.h b/modules/processing/volume_control/lib/inc/SoftVolumeControls.h new file mode 100644 index 0000000..b8d9dd4 --- /dev/null +++ b/modules/processing/volume_control/lib/inc/SoftVolumeControls.h @@ -0,0 +1,293 @@ +/* + * Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + * SPDX-License-Identifier: BSD-3-Clause-Clear + */ +/***************************************************************************** + * FILE NAME: SoftVolumeControls.h + * DESCRIPTION: + * Volume and Balance Controls +******************************************************************************/ + +#ifndef _SOFTVOLUMECONTROLS_H_ +#define _SOFTVOLUMECONTROLS_H_ +#include "audio_comdef.h" + +/*============================================================================= + Constants +=============================================================================*/ +static const uint32 RAMP_LINEAR = 0; +static const uint32 RAMP_EXP = 1; +static const uint32 RAMP_LOG = 2; +static const uint32 RAMP_FRACT_EXP = 3; +static const uint32 PAUSE_RAMP_COMPLETE = 1; +/*===========================================================================*/ +/* */ +/* current/---------------- <- targetgainL16Q12 */ +/* time / . */ +/* start . / . */ +/* panning . / . */ +/* . v/..................... index */ +/* . / . */ +/* . / . */ +/* v / . */ +/* /. . . . . . . . . . . . <- currentgainL16Q12 */ +/* . */ +/* index to the current ramp sample */ +/* |<------->| */ +/* sampleCounter */ +/* */ +/* ------------------------------------------> time */ +/* (as the panner works, sampleCounter will decrease until it becomes zero, */ +/* and at that moment, gain of the panner should reach targetGainL16Q15) */ +/* (angle panner has similar structure, can use same graph as reference) */ +/*===========================================================================*/ + +struct linearCurveCoefficients +{ + /* for a linear ramp curve */ + int64 currentGainL64Q59; + int64 deltaL64Q59; /* Delta is the difference in consecutive gains */ +}; + +struct expCurveCoefficients +{ + int32 BL32Q26; /* Exp curve equation variables */ + int32 AL32Q26; + int32 CL32Q26; + int32 deltaCL32Q26; +}; + +struct logCurveCoefficients +{ + int32 AL32Q26; /* Additional Log curve equation variables */ + int32 BL32Q26; + int32 CL32Q16; + int32 deltaCL32Q16; +}; + +struct fractExpCurveCoefficients +{ + uint32 AL32Q28; /* Fractional exponent curve equation variables */ + uint32 BL32Q28; + uint32 CL32Q31; + int32 deltaCL32Q31; +}; + +union curveCoefficients +{ + linearCurveCoefficients linear; + expCurveCoefficients exp; + logCurveCoefficients log; + fractExpCurveCoefficients fract; +}; + +struct SvpannerStruct +{ + uint32 targetgainL32Q28; /* Ramp to this gain value */ + uint32 currentGainL32Q28; /* Ramp from this gain value */ + uint32 sampleCounter; /* Period of ramping */ + + curveCoefficients coeffs; + + uint32 index; /* index to the current ramp sample */ + uint32 step; /* step is the number of samples on which the currentgainL16Q12 is + applied before the calculating the new currentgainL16Q12 */ + uint32 stepResidue; /* Number of samples in the previous frame to which the currentgainL16Q12 + has been applied. step-stepresidue are the number of samples to which the currentGainL16Q12 + should be applied for the current frame */ + uint32 rampingCurve; + uint32 newGainL32Q28; +}; + +struct SoftSteppingParams +{ + uint32 periodMs; // Soft stepping period in ms + uint32 rampingCurve; // Use the RAMP_* constants defined in this file. + uint32 stepUs; // Soft stepping step in us. +}; + +struct perChannelData +{ + uint32 chanGainQ28; + boolean isMuted; // Indicates whether muted or not + SvpannerStruct panner; +}; + +struct PerChannelDataBlock +{ + int8_t * inPtr; + int8_t * outPtr; + uint32_t sampleCount; + perChannelData channelStruct; +}; + +struct MuteBeforeRampParams +{ + uint32 m_resumeWithDelayInMs; //time in ms for which the module is muted before resume rampup + uint32 m_muteBeforeRampSamples; //number of mute samples before ramping up + uint32 m_muteSamplesPending; //number of mute samples pending before ramping up + +}; + +class CSoftVolumeControlsLib +{ + + private: + enum PauseState + { + STEADY, + RAMPING_DOWN, + PAUSE, + WAITING, + RAMPING_UP, + MUTE_BEFORE_RAMPUP + }; + + enum PauseCommand + { + COMMAND_PAUSE, + COMMAND_RESUME, + COMMAND_FORCE_PAUSE, + COMMAND_INIT + }; + + uint32 m_sampleRate; // Sampling rate of the stream + uint32 m_bytesPerSample; + boolean m_isPaused; // Indicates whether paused or not + SoftSteppingParams m_softVolumeParams; + SoftSteppingParams m_softMuteParams; + SoftSteppingParams m_softPauseParams; + SoftSteppingParams m_softResumeParams; + MuteBeforeRampParams m_muteBeforeResumeParams; + PauseState m_pauseState; + + uint32 m_thresholdQ15; // threshold 16-bit + uint32 m_thresholdQ27; // threshold 24-bit + uint32 m_thresholdQ31; // threshold 32-bit + uint32 m_qFactor; + + public: + CSoftVolumeControlsLib(); + ~CSoftVolumeControlsLib(); + + // Functions for handling the per channel structures + static uint32 GetSizeOfPerChannelStruct(void); + void InitializePerChannelStruct(void *pChannelStruct); + /* + * This function is to be use for the following case: + * If a media type comes during ramping and some channel goes + * away, the data from that channel will immediately stop. So + * the state of the panner will stay in the middle of ramping. + * When the channel re-appears, we do not want it to continue + * ramping from the old state since the other channels will + * have finished ramping by this point. + * + * Hence, this function should be called whenever a new channel + * appears in the data, to ensure that any previous ramping + * state is discarded and it starts immediately from its target + * gain. + */ + void GoToTargetGainImmediately(void *pChannelStruct); + + //returns the target gain. + uint32 GetTargetGain(void *pChannelStruct); + + // Functions to process the data stream + /* Lib function takes in ptr, out ptr, sample count and the channel struct for each channel + * To be used when individual channels can be processed at a time */ + void Process(void *pInPtr, void *pOutPtr, const uint32 nSampleCnt, void *pChannelStruct); + + /* Function takes all channels' data as input. Loops over each channel inside the lib. + * Sets flag is_paused to true when module goes to pause state after processing all channels + * To be used when common flag is to be set over all channels just once */ + void ProcessAllChannels(PerChannelDataBlock *pChannels, const uint32 nChannelCnt, uint8_t *is_paused); + + // Threshold related functions + void SetThreshold(uint32 pThreshold_dBfs); + uint32 GetThreshold(void) const; + int32 DetectThreshold(void *pInPtr, const uint32 nSampleCnt); + + // Media format related functions + void SetSampleRate(uint32_t oldSampleRate, uint32_t newSampleRate, void *pChannelStruct); + uint32 GetSampleRate(void) const; + void SetBytesPerSample(const uint32_t bytesPerSample); + void SetQFactor(const uint32 qFactor); + uint32 GetBytesPerSample(void) const; + + // Volume related functions + void SetVolume(const uint32_t gainQ28, void *pChannelStruct); + void SetSoftVolumeParams(const SoftSteppingParams &softVolumeParams); + void SetSoftMuteParams(const SoftSteppingParams &softMuteParams); + void GetSoftVolumeParams(SoftSteppingParams *pSoftVolumeParams) const; + void GetSoftMuteParams(SoftSteppingParams *pSoftMuteParams) const; + + // Mute related functions. + // Note: There is no soft stepping on mute/unmute. + void Mute(void *pChannelStruct); + void Unmute(void *pChannelStruct); + boolean IsMuted(const void *pChannelStruct) const; + + // Pause related parameters. + void SetSoftPauseParams(const SoftSteppingParams &softPauseParams); + void SetSoftResumeParams(const SoftSteppingParams &softResumeParams); + void SetResumeWithDelayParam(const uint32 &resumeWithDelayParam); + uint32 GetResumeWithDelayParam() const; + void GetSoftPauseParams(SoftSteppingParams *pSoftPauseParams) const; + void GetSoftResumeParams(SoftSteppingParams *pSoftPauseParams) const; + void StartSoftPause(void *pChannelStruct); // Has to be called for every channel when pausing + void StartSoftPauseAllChannels(PerChannelDataBlock *pChannels, const uint32 nChannelCnt); + void StartSoftResume(void *pChannelStruct); // Has to be called for every channel when resuming + void StartSoftResumeAllChannels(PerChannelDataBlock *pChannels, const uint32 nChannelCnt); + void ForcePause(PerChannelDataBlock *pChannels, const uint32 nChannelCnt); // Forces transition to PAUSE state. + uint32 GetPauseRampPeriod() const; + uint32 GetPauseState() const; + + // Will return true if the library is applying a steady state unity gain to the channel. + // Can be used for optimization - no need to call the library if this returns true. + boolean isUnityGain(const void *pChannelStruct) const; + + private: + // Functions for applying the ramps + void ApplyLinearRamp(void *pOutPtr, void *pInPtr, SvpannerStruct *panner, uint32 samples); + void ApplyLogRamp(void *pOutPtr, void *pInPtr, SvpannerStruct *panner, uint32 samples); + void ApplyExpRamp(void *pOutPtr, void *pInPtr, SvpannerStruct *panner, uint32 samples); + void ApplyFractExpRamp(void *pOutPtr, void *pInPtr, SvpannerStruct *panner, uint32 samples); + + // Functions for setting up the panners + void SetupPanner(SvpannerStruct *panner, uint32 newGainL32Q28, const SoftSteppingParams ¶ms); + void SoftPannerLinearSetup(SvpannerStruct *panner, /* panner struct */ + uint32 newGainL32Q28, /* new target panner gain */ + uint32 rampSamples, /* number of samples in the ramp */ + uint32 step); + void SoftPannerExpSetup(SvpannerStruct *panner, /* panner struct */ + uint32 newGainL32Q28, /* new target panner gain in Q28 */ + uint32 rampSamples, /* number of samples in the ramp */ + uint32 step); + void SoftPannerLogSetup(SvpannerStruct *panner, /* panner struct */ + uint32 newGainL32Q28, /* new target panner gain */ + uint32 rampSamples, /* number of samples in the ramp */ + uint32 step); + void SoftPannerFractExpSetup(SvpannerStruct *panner, /* panner struct */ + uint32 newGainL32Q28, /* new target panner gain */ + uint32 rampSamples, /* number of samples in the ramp */ + uint32 step); + + void ApplySteadyGain(void *pOutPtr, void *pInPtr, const uint32 gainQ28, uint32 samples); + void ApplySteadyGain16(void *pOutPtr, void *pInPtr, const uint32 gainQ28, uint32 samples); + void ApplySteadyGain32(void *pOutPtr, void *pInPtr, const uint32 gainQ28, uint32 samples); + void IncrementPointer(void **pPtr, uint32 samples); + + // Threshold functions + int32 DetectThreshold16(void *pInPtr, const uint32 nSampleCnt); + int32 DetectThreshold24(void *pInPtr, const uint32 nSampleCnt); + int32 DetectThreshold32(void *pInPtr, const uint32 nSampleCnt); + + // process function + boolean ProcessV2SingleChannel(void *inPtr, void *pOutPtr, const uint32 nSampleCnt, void *pChannelStruct); + + // State management. + boolean ShouldRespond(PauseCommand command); + void StateTransition(PauseCommand command); +}; + +#endif // _SOFTVOLUMECONTROLS_H_ diff --git a/modules/processing/volume_control/lib/inc/apply_gain.h b/modules/processing/volume_control/lib/inc/apply_gain.h new file mode 100644 index 0000000..8f7c579 --- /dev/null +++ b/modules/processing/volume_control/lib/inc/apply_gain.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + * SPDX-License-Identifier: BSD-3-Clause-Clear + */ +/*=========================================================================== + * FILE NAME: apply_gain.h + * DESCRIPTION: + * Volume and Balance Controls + *===========================================================================*/ + + +#ifndef _APPLYGAIN_H_ +#define _APPLYGAIN_H_ + +#include "audio_basic_op.h" +#include "audio_basic_op_ext.h" + +#ifdef __cplusplus +extern "C" { +#endif //__cplusplus + +void audpp_volume_apply_gain_16(int16* outptr, /* Pointer to output */ + int16* inptr, /* Pointer to input */ + uint16 gain, /* Gain in Q12 format */ + uint32 samples); /* No of samples to which the gain is to be applied */ + +void audpp_volume_apply_gain_32_G1(int32* outptr, /* Pointer to output */ + int32* inptr, /* Pointer to input */ + uint16 gain, /* Gain in Q12 format */ + uint32 samples); /* No of samples to which the gain is to be applied */ + +void audpp_volume_apply_gain_32_L1(int32* outptr, /* Pointer to output */ + int32* inptr, /* Pointer to input */ + uint16 gain, /* Gain in Q12 format */ + uint32 samples); /* No of samples to which the gain is to be applied */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif // _APPLYGAIN_H + diff --git a/modules/processing/volume_control/lib/src/apply_gain.c b/modules/processing/volume_control/lib/src/apply_gain.c new file mode 100644 index 0000000..6b0eaa3 --- /dev/null +++ b/modules/processing/volume_control/lib/src/apply_gain.c @@ -0,0 +1,244 @@ +/* + * Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + * SPDX-License-Identifier: BSD-3-Clause-Clear + */ +/*=========================================================================== + * FILE NAME: apply_gain.c + * DESCRIPTION: + * Apply gain (Q12) to the input buffer + *===========================================================================*/ + +#ifdef AVS_BUILD_SOS +#include "capi_cmn.h" +#endif +#include "apply_gain.h" +#include "ar_msg.h" +/*============================================================================= +FUNCTION void audpp_volume_apply_gain_16 + +DESCRIPTION apply gain (Q12) to the input buffer (Q15), shift and place the result in the + output buffer (Q15). Optimized for Q6 by loading 2 values of input at the same time. +OUTPUTS + +DEPENDENCIES None + +RETURN VALUE None + +SIDE EFFECTS +===============================================================================*/ + +void audpp_volume_apply_gain_16(int16* outptr, /* Pointer to output */ + int16* inptr, /* Pointer to input */ + uint16 gain, /* Gain in Q12 format */ + uint32 samples) /* No of samples to which the gain is to be applied */ +{ +#if ((defined __hexagon__) || (defined __qdsp6__)) + int32 nGainQ15 = (Q6_R_zxth_R(gain)) << 3; /* scale the gain up by 3 bit to become Q15 for ease of truncation*/ + int64 nCombinedGain = Q6_P_combine_RR(nGainQ15, nGainQ15); /* combine int32 into int64 for vector operation*/ + + int64 nInputCombinedP1,nInputCombinedP2,nInputCombinedP3,nInputCombinedP4; + int64 temp1,temp2,temp3,temp4; + + // if output buffer doesn't start with 8 byte (or 4 half-word) boundary, it must start with 2 byte boundary + while ((0 != (((uint32)outptr) & 0x6)) && (samples)) + { + // when output buffer address doesn't end with 000, such as in 0b...xy0, x|y=1 + *outptr++ = s16_saturate_s32(s32_mult_s32_s16_rnd_sat(nGainQ15, *inptr++)); + samples--; + } + + // The rest will be computed with two vector mults in a row, ie, 2 samples a packet, 4 samples per iteration + int64 *pALGN8OutPtr = (int64 *)outptr; + while (samples >=8 ) + { + // apply gain to left channel + nInputCombinedP1 = Q6_P_combine_RR(inptr[1],inptr[0]); + nInputCombinedP2 = Q6_P_combine_RR(inptr[3],inptr[2]); + nInputCombinedP3 = Q6_P_combine_RR(inptr[5],inptr[4]); + nInputCombinedP4 = Q6_P_combine_RR(inptr[7],inptr[6]); + samples-=8; + inptr+= 8; + + temp1 = Q6_P_vmpyweh_PP_s1_rnd_sat(nCombinedGain, nInputCombinedP1); + temp2 = Q6_P_vmpyweh_PP_s1_rnd_sat(nCombinedGain, nInputCombinedP2); + temp3 = Q6_P_vmpyweh_PP_s1_rnd_sat(nCombinedGain, nInputCombinedP3); + temp4 = Q6_P_vmpyweh_PP_s1_rnd_sat(nCombinedGain, nInputCombinedP4); + + *pALGN8OutPtr++ = Q6_P_combine_RR(Q6_R_vsatwh_P(temp2),Q6_R_vsatwh_P(temp1)); + *pALGN8OutPtr++ = Q6_P_combine_RR(Q6_R_vsatwh_P(temp4),Q6_R_vsatwh_P(temp3)); + } + outptr = (int16 *)pALGN8OutPtr; + // if there are less than 4 samples left, + while (samples--) + { + *outptr++ = s16_saturate_s32(s32_mult_s32_s16_rnd_sat(nGainQ15, *inptr++)); + } +#else + //AR_MSG(DBG_HIGH_PRIO, "apply gain level 2"); + /*--------------------------------------- Unoptimized non q6 vesion-----------------------------------------------*/ + int32 nGainQ15 = ((uint32)(gain)) << 3; /* scale the gain up by 3 bit to become Q15 for ease of truncation*/ + + //Replaced while(samples--) with for loop because samples is unsigned integer + //Sanitizer throws unsigned overflow error during build on ARM + for (int i = 0; i < samples; i++) + { + *outptr++ = s16_saturate_s32(s32_mult_s32_s16_rnd_sat(nGainQ15, *inptr++)); + } + +#endif +} + +/*============================================================================= +FUNCTION void audpp_volume_apply_gain_32_G1 + +DESCRIPTION apply gain (Q12) to the input buffer (Q31), shift and place the result in the + output buffer (Q31). +OUTPUTS + +DEPENDENCIES For Gain value greater than 1. + +RETURN VALUE None + +SIDE EFFECTS +===============================================================================*/ + +void audpp_volume_apply_gain_32_G1(int32* outptr, /* Pointer to output */ + int32* inptr, /* Pointer to input */ + uint16 gain, /* Gain in Q12 format */ + uint32 samples) /* No of samples to which the gain is to be applied */ +{ + +#if ((defined __hexagon__) || (defined __qdsp6__)) + /*--------------------------------------- q6 vesion-----------------------------------------------*/ + + int32 nGainQ15 = (Q6_R_zxth_R(gain)) << 3; /* scale the gain up by 3 bit to become Q15 for ease of truncation*/ + int64 temp1,temp2; + + temp1 = Q6_P_asrrnd_PI(Q6_P_mpy_RR(*inptr++, nGainQ15),15); + + // if output buffer doesn't start with 8 byte (or 4 half-word) boundary, it must start with 4 byte boundary + while ((0 != (((uint32)outptr) & 0x4)) && (samples)) + { + // when output buffer address doesn't end with 000 + *outptr++ = Q6_R_sat_P(temp1); + samples--; + if(samples) + { + temp1 = Q6_P_asrrnd_PI(Q6_P_mpy_RR(*inptr++, nGainQ15),15); + } + } + + if(0==samples) + return; + + int64 *pALGN8OutPtr = (int64 *)outptr; + temp2 = 0; + if(samples>1) + { + temp2 = Q6_P_asrrnd_PI(Q6_P_mpy_RR(*inptr++, nGainQ15),15); + } + else + { + *outptr++ = Q6_R_sat_P(temp1); + return; + } + + while (samples>3) + { + *pALGN8OutPtr++ = Q6_P_combine_RR(Q6_R_sat_P(temp2),Q6_R_sat_P(temp1)); + temp1 = Q6_P_asrrnd_PI(Q6_P_mpy_RR(*inptr++, nGainQ15),15); + temp2 = Q6_P_asrrnd_PI(Q6_P_mpy_RR(*inptr++, nGainQ15),15); + samples-=2; + } + *pALGN8OutPtr++ = Q6_P_combine_RR(Q6_R_sat_P(temp2),Q6_R_sat_P(temp1)); + samples-=2; + outptr = (int32 *)pALGN8OutPtr; + + if(samples) + { + temp1 = Q6_P_asrrnd_PI(Q6_P_mpy_RR(*inptr++, nGainQ15),15); + *outptr++ = Q6_R_sat_P(temp1); + } + +#else + int32 nGainQ15 = ((uint32)gain) << 3; /* scale the gain up by 3 bit to become Q15 for ease of truncation*/ + + /*--------------------------------------- Non q6 vesion-----------------------------------------------*/ + + //Replaced while(samples--) with for loop because samples is unsigned integer + //Sanitizer throws unsigned overflow error during build on ARM + for (int i = 0; i < samples; i++) + { + *outptr++ = s32_saturate_s64((s64_mult_s32_s32(*inptr++, nGainQ15) + 0x4000) >> 15); + } +#endif +} + +/*============================================================================= +FUNCTION void audpp_volume_apply_gain_32_L1 + +DESCRIPTION apply gain (Q12) to the input buffer (Q31), shift and place the result in the + output buffer (Q31). +OUTPUTS + +DEPENDENCIES For Gain value less than 1. + +RETURN VALUE None + +SIDE EFFECTS +===============================================================================*/ +void audpp_volume_apply_gain_32_L1(int32* outptr, /* Pointer to output */ + int32* inptr, /* Pointer to input */ + uint16 gain, /* Gain in Q12 format */ + uint32 samples) /* No of samples to which the gain is to be applied */ +{ + + int16 nGainQ15 = gain << 3; /* scale the gain up by 3 bit to become Q15 for ease of truncation*/ + +#if ((defined __hexagon__) || (defined __qdsp6__)) + /*--------------------------------------- optimized q6 vesion-----------------------------------------------*/ + + int64 nCombinedGain = Q6_P_combine_RR(nGainQ15,nGainQ15); + int64 nInputCombinedP1,nInputCombinedP2; + + // if output buffer doesn't start with 8 byte (or 4 half-word) boundary, it must start with 4 byte boundary + while ((0 != (((uint32)outptr) & 0x4)) && (samples)) + { + // when output buffer address doesn't end with 000 + *outptr++ = s32_mult_s32_s16_rnd_sat(*inptr++, nGainQ15); + samples--; + } + + // The rest will be computed with two vector mults in a row, ie, 2 samples a packet, 4 samples per iteration + int64 *pALGN8OutPtr = (int64 *)outptr; + while (samples >=4 ) + { + // apply gain to left channel + nInputCombinedP1 = Q6_P_combine_RR(inptr[1],inptr[0]); + nInputCombinedP2 = Q6_P_combine_RR(inptr[3],inptr[2]); + + samples-=4; + inptr+= 4; + + *pALGN8OutPtr++ = Q6_P_vmpyweh_PP_s1_rnd_sat(nInputCombinedP1,nCombinedGain); + *pALGN8OutPtr++ = Q6_P_vmpyweh_PP_s1_rnd_sat(nInputCombinedP2,nCombinedGain); + } + outptr = (int32 *)pALGN8OutPtr; + // if there are less than 4 samples left, + while (samples--) + { + *outptr++ = s32_mult_s32_s16_rnd_sat(*inptr++, nGainQ15); + } +#else + /*--------------------------------------- Unoptimized non q6 vesion-----------------------------------------------*/ + + //Replaced while(samples--) with for loop because samples is unsigned integer + //Sanitizer throws unsigned overflow error during build on ARM + for (int i = 0; i < samples; i++) + { + *outptr++ = s32_mult_s32_s16_rnd_sat(*inptr++, nGainQ15); + } +#endif +} + + diff --git a/modules/processing/volume_control/lib/src/softvolumecontrols.cpp b/modules/processing/volume_control/lib/src/softvolumecontrols.cpp new file mode 100644 index 0000000..5bab0b7 --- /dev/null +++ b/modules/processing/volume_control/lib/src/softvolumecontrols.cpp @@ -0,0 +1,783 @@ +/* + * Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + * SPDX-License-Identifier: BSD-3-Clause-Clear + */ +/*=========================================================================== + * FILE NAME: SoftVolumeControls.cpp + * DESCRIPTION: + * Volume and Soft stepping + *===========================================================================*/ + +#ifdef AVS_BUILD_SOS +#include "capi_cmn.h" +#endif + +#include "SoftVolumeControls.h" +#include "audio_basic_op.h" +#include "audio_divide_qx.h" +#include "audio_log10.h" +#include "audio_basic_op_ext.h" +#include +#include "audio_exp10.h" +#include "apply_gain.h" +#include "audio_clips.h" +#include "string.h" +#include "posal.h" + +#if ((defined __hexagon__) || (defined __qdsp6__)) +#include +#include +#include +// #include "OmmUtils.h" +#endif + + +static const uint32 DEFAULT_THRESHOLD = 0; +static const int32 K_expL32Q26 = (10 << 26); +static const int32 M_expL32Q31 = 2099202; // M = 1/(2^K -1) +static const int32 K_logL32Q16 = (200 << 16); +static const int32 M_logL32Q31 = 280678230; // M = 1/log2(1+K) + +static const uint32 UNITY_L32_Q28 = 1 << 28; + +CSoftVolumeControlsLib::CSoftVolumeControlsLib() + : m_sampleRate(48000), m_bytesPerSample(2), m_isPaused(false), m_thresholdQ15(0) +{ + m_softVolumeParams.periodMs = 0; + m_softVolumeParams.rampingCurve = RAMP_LINEAR; + m_softVolumeParams.stepUs = 0; + + m_softMuteParams.periodMs = 0; + m_softMuteParams.rampingCurve = RAMP_LINEAR; + m_softMuteParams.stepUs = 0; + + m_softPauseParams.periodMs = 0; + m_softPauseParams.rampingCurve = RAMP_LINEAR; + m_softPauseParams.stepUs = 0; + + m_softResumeParams.periodMs = 0; + m_softResumeParams.rampingCurve = RAMP_LINEAR; + m_softResumeParams.stepUs = 0; + + SetThreshold(DEFAULT_THRESHOLD); + m_muteBeforeResumeParams.m_muteBeforeRampSamples = 0; + m_muteBeforeResumeParams.m_muteSamplesPending = 0; + m_muteBeforeResumeParams.m_resumeWithDelayInMs = 0; +} + +CSoftVolumeControlsLib::~CSoftVolumeControlsLib() +{ +} + +void CSoftVolumeControlsLib::SetSoftPauseParams(const SoftSteppingParams &softPauseParams) +{ + m_softPauseParams = softPauseParams; + + // Ensure that the step is not greater than the period + uint32 periodUs = m_softPauseParams.periodMs * 1000; + if (m_softPauseParams.stepUs > periodUs) + { + m_softPauseParams.stepUs = periodUs; + } +} + +void CSoftVolumeControlsLib::SetSoftResumeParams(const SoftSteppingParams &softResumeParams) +{ + m_softResumeParams = softResumeParams; + + // Ensure that the step is not greater than the period + uint32 periodUs = m_softResumeParams.periodMs * 1000; + if (m_softResumeParams.stepUs > periodUs) + { + m_softResumeParams.stepUs = periodUs; + } +} + +void CSoftVolumeControlsLib::SetSoftVolumeParams(const SoftSteppingParams &softVolumeParams) +{ + m_softVolumeParams = softVolumeParams; + + // Ensure that the step is not greater than the period + uint32 periodUs = m_softVolumeParams.periodMs * 1000; + if (m_softVolumeParams.stepUs > periodUs) + { + m_softVolumeParams.stepUs = periodUs; + } +} + +void CSoftVolumeControlsLib::SetSoftMuteParams(const SoftSteppingParams &softMuteParams) +{ + m_softMuteParams = softMuteParams; + + // Ensure that the step is not greater than the period + uint32 periodUs = m_softMuteParams.periodMs * 1000; + if (m_softMuteParams.stepUs > periodUs) + { + m_softMuteParams.stepUs = periodUs; + } +} + +void CSoftVolumeControlsLib::SetResumeWithDelayParam(const uint32 &delayResumeParam) +{ + m_muteBeforeResumeParams.m_resumeWithDelayInMs = delayResumeParam; +} + +boolean CSoftVolumeControlsLib::IsMuted(const void *pChannelStruct) const +{ + const perChannelData *pChannelData = reinterpret_cast(pChannelStruct); + + return pChannelData->isMuted; +} + +void CSoftVolumeControlsLib::Unmute(void *pChannelStruct) +{ + perChannelData *pChannelData = reinterpret_cast(pChannelStruct); + + if (pChannelData->isMuted) + { + pChannelData->isMuted = false; + + if (!m_isPaused) + { + SetupPanner(&pChannelData->panner, pChannelData->chanGainQ28, m_softMuteParams); + } + } +} + +void CSoftVolumeControlsLib::Mute(void *pChannelStruct) +{ + perChannelData *pChannelData = reinterpret_cast(pChannelStruct); + + if (!pChannelData->isMuted) + { + pChannelData->isMuted = true; + + SetupPanner(&pChannelData->panner, 0, m_softMuteParams); + } +} + +void CSoftVolumeControlsLib::SetThreshold(uint32 pThreshold_Q15) +{ + // todo: should there be a limit check + if (pThreshold_Q15 == m_thresholdQ15) + { + return; + } + + m_thresholdQ15 = pThreshold_Q15; // directly getting it in Q15 + m_thresholdQ27 = pThreshold_Q15 << 12; // 27-15 + m_thresholdQ31 = pThreshold_Q15 << 16; // 31-15 +} + +uint32 CSoftVolumeControlsLib::GetThreshold() const +{ + return m_thresholdQ15; +} + +uint32 CSoftVolumeControlsLib::GetPauseRampPeriod() const +{ + return m_softPauseParams.periodMs; +} + +/*============================================================================= +FUNCTION void soft_panner_log_setup + +DESCRIPTION Setup Panner for log ramp +OUTPUTS + +DEPENDENCIES None + +RETURN VALUE None + +SIDE EFFECTS +===============================================================================*/ + +void CSoftVolumeControlsLib::SoftPannerLogSetup(SvpannerStruct *panner, /* panner struct */ + uint32 newGainL32Q28, /* new target panner gain */ + uint32 rampSamples, /* number of samples in the ramp */ + uint32 step) +{ + /* substitute panner struct values with shorter names */ + uint32 currentGainL32Q28 = panner->currentGainL32Q28; + int32 AL32Q26 = 0; + int32 BL32Q26 = 0; + int32 CL32Q16 = 0; + int32 deltaCL32Q16 = 0; + uint32 sampleCounter = 0; + panner->newGainL32Q28 = newGainL32Q28; + + if (step <= 0) + { + step = 1; + } + + /*----------------------------- if no ramp ------------------------------*/ + if (rampSamples <= 0) + { + currentGainL32Q28 = newGainL32Q28; + } /* end of if (rampSamples <= 0) */ + /*--------------- if there is ramp, then need some work here ------------*/ + else /* (rampSamples > 0) */ + { + if (newGainL32Q28 != currentGainL32Q28) + { + /*------ determine change -------*/ + int32 newGainL32Q27 = (int32)(newGainL32Q28 >> (28 - 27)); + int32 currentGainL32Q27 = (int32)(currentGainL32Q28 >> (28 - 27)); + + sampleCounter = rampSamples; + int32 changeL32Q27 = newGainL32Q27 - currentGainL32Q27; + int32 currentGainL32Q26 = currentGainL32Q27 >> (27 - 26); + + AL32Q26 = s32_mult_s32_s32_rnd_sat(changeL32Q27, M_logL32Q31); + BL32Q26 = currentGainL32Q26; + CL32Q16 = 1 << 16; + deltaCL32Q16 = (K_logL32Q16 / sampleCounter * step); + } + } /* end of else (rampSamples > 0) */ + + /*------------- store new values back into panner struct ----------------*/ + + panner->step = step; + panner->stepResidue = step; + panner->coeffs.log.BL32Q26 = BL32Q26; + panner->coeffs.log.AL32Q26 = AL32Q26; + panner->coeffs.log.CL32Q16 = CL32Q16; + panner->coeffs.log.deltaCL32Q16 = deltaCL32Q16; + panner->targetgainL32Q28 = newGainL32Q28; // update target gain + panner->sampleCounter = sampleCounter; + panner->currentGainL32Q28 = currentGainL32Q28; + panner->index = 0; +} /*------------------- end of function soft_panner_log_setup------------------*/ + +/*============================================================================= +FUNCTION void soft_panner_linear_setup + +DESCRIPTION Setup Panner for linear ramp +OUTPUTS + +DEPENDENCIES None + +RETURN VALUE None + +SIDE EFFECTS +===============================================================================*/ +void CSoftVolumeControlsLib::SoftPannerLinearSetup(SvpannerStruct *panner, /* panner struct */ + uint32 newGainL32Q28, /* new target panner gain */ + uint32 rampSamples, /* number of samples in the ramp */ + uint32 step) +{ + + /* substitute panner struct values with shorter names */ + uint32 sampleCounter = panner->sampleCounter; + uint32 currentGainL32Q28 = panner->currentGainL32Q28; + int64 deltaL64Q59; + + /*----------------------------- if no ramp ------------------------------*/ + if (rampSamples <= 0) + { + sampleCounter = 0; + deltaL64Q59 = 0; + currentGainL32Q28 = newGainL32Q28; /* rampSamples =0 then apply the + new value of gain immediately*/ + + } /* end of if (rampSamples <= 0) */ + + /*--------------- if there is ramp, then need some work here ------------*/ + else /* (rampSamples > 0) */ + { + + /*------ determine change -------*/ + /* if no change, then reset counter and delta */ + if (newGainL32Q28 == currentGainL32Q28) + { + + sampleCounter = 0; + deltaL64Q59 = 0; + } + /* else, set counter and calculate delta */ + else + { + + sampleCounter = rampSamples; + if (0 == step) + { + step = 1; + } + + uint64 stepL64Q31 = ((uint64)(step)) << 31; + uint32 stepOverSamplesL32Q31 = (uint32)(stepL64Q31 / rampSamples); + uint64 scaledCurrentGainL64Q59 = u64_mult_u32_u32(currentGainL32Q28, stepOverSamplesL32Q31); + uint64 scaledNewGainL64Q59 = u64_mult_u32_u32(newGainL32Q28, stepOverSamplesL32Q31); + + deltaL64Q59 = scaledNewGainL64Q59 - + scaledCurrentGainL64Q59; // Since the max gain value is 16, there will be no wraparound. + } + } /* end of else (rampSamples > 0) */ + + /*------------- store new values back into panner struct ----------------*/ + panner->step = step; + panner->stepResidue = 0; + panner->coeffs.linear.deltaL64Q59 = deltaL64Q59; + panner->targetgainL32Q28 = newGainL32Q28; // update target gain + panner->sampleCounter = sampleCounter; + panner->currentGainL32Q28 = currentGainL32Q28; + panner->coeffs.linear.currentGainL64Q59 = ((int64)currentGainL32Q28) << (59 - 28); + panner->index = 0; + +} /*------------------- end of function soft_panner_linear_setup --------------*/ + +/*============================================================================= +FUNCTION void soft_panner_exp_setup + +DESCRIPTION Setup Panner for an exponential ramp +OUTPUTS + +DEPENDENCIES None + +RETURN VALUE None + +SIDE EFFECTS +===============================================================================*/ +void CSoftVolumeControlsLib::SoftPannerExpSetup(SvpannerStruct *panner, /* panner struct */ + uint32 newGainL32Q28, /* new target panner gain in Q28 */ + uint32 rampSamples, /* number of samples in the ramp */ + uint32 step) +{ + /* substitute panner struct values with shorter names */ + uint32 currentGainL32Q28 = panner->currentGainL32Q28; + int32 AL32Q26 = 0; + int32 BL32Q26 = 0; + int32 CL32Q26 = 0; + int32 deltaCL32Q26 = 0; + uint32 sampleCounter = 0; + + if (step <= 0) + { + step = 1; + } + + /*----------------------------- if no ramp ------------------------------*/ + if (rampSamples <= 0) + { + currentGainL32Q28 = newGainL32Q28; + } /* end of if (rampSamples <= 0) */ + /*--------------- if there is ramp, then need some work here ------------*/ + else /* (rampSamples > 0) */ + { + if (newGainL32Q28 != currentGainL32Q28) + { + /*------ determine change -------*/ + int32 newGainL32Q27 = (int32)(newGainL32Q28 >> (28 - 27)); + int32 currentGainL32Q27 = (int32)(currentGainL32Q28 >> (28 - 27)); + + sampleCounter = rampSamples; + int32 changeL32Q27 = newGainL32Q27 - currentGainL32Q27; + int32 currentGainL32Q26 = currentGainL32Q27 >> (27 - 26); + + AL32Q26 = s32_mult_s32_s32_rnd_sat(changeL32Q27, M_expL32Q31); + BL32Q26 = s32_sub_s32_s32_sat(currentGainL32Q26, AL32Q26); + deltaCL32Q26 = (K_expL32Q26 / sampleCounter * step); + } + } /* end of else (rampSamples > 0) */ + + /*------------- store new values back into panner struct ----------------*/ + + panner->step = step; + panner->stepResidue = step; + panner->coeffs.exp.BL32Q26 = BL32Q26; + panner->coeffs.exp.AL32Q26 = AL32Q26; + panner->coeffs.exp.CL32Q26 = CL32Q26; + panner->coeffs.exp.deltaCL32Q26 = deltaCL32Q26; + panner->targetgainL32Q28 = newGainL32Q28; // update target gain + panner->sampleCounter = sampleCounter; + panner->currentGainL32Q28 = currentGainL32Q28; + panner->index = 0; +} /*------------------- end of function soft_panner_exp_setup --------------*/ + +boolean CSoftVolumeControlsLib::ShouldRespond(PauseCommand command) +{ + bool should_respond = false; + switch (command) + { + case COMMAND_PAUSE: + { + if (m_pauseState == STEADY || m_pauseState == WAITING || m_pauseState == RAMPING_UP || + MUTE_BEFORE_RAMPUP == m_pauseState) + { + should_respond = true; + } + break; + } + case COMMAND_FORCE_PAUSE: + { + if (m_pauseState != PAUSE) + should_respond = true; + break; + } + case COMMAND_RESUME: + { + if (m_pauseState == PAUSE || m_pauseState == RAMPING_DOWN) + { + should_respond = true; + } + break; + } + default: + { + break; + } + } + + return should_respond; +} + +// Note that ProcessV2() can also cause state transitions. +void CSoftVolumeControlsLib::StateTransition(PauseCommand command) +{ + switch (command) + { + case COMMAND_INIT: + { + m_pauseState = STEADY; + break; + } + case COMMAND_PAUSE: + { + // Unless the module is paused/ramping down, any other state transitions to ramping down + // TODO: Check MUTE_BEFORE_RAMPUP state usage + if (m_pauseState == STEADY || m_pauseState == RAMPING_UP || m_pauseState == WAITING || + m_pauseState == MUTE_BEFORE_RAMPUP) + { + m_pauseState = RAMPING_DOWN; + } + break; + } + case COMMAND_FORCE_PAUSE: + { + m_pauseState = PAUSE; + break; + } + case COMMAND_RESUME: + { + if (m_pauseState == PAUSE || m_pauseState == RAMPING_DOWN) + { + m_pauseState = WAITING; + } + break; + } + default: + { + break; + } + } +} + +void CSoftVolumeControlsLib::SetVolume(const uint32_t gainQ28, void *pChannelStruct) +{ + perChannelData *pChannelData = reinterpret_cast(pChannelStruct); + + pChannelData->chanGainQ28 = gainQ28; + + if (!m_isPaused && !pChannelData->isMuted) + { + // Ramp to target gain + SetupPanner(&pChannelData->panner, pChannelData->chanGainQ28, m_softVolumeParams); + } +} + +void CSoftVolumeControlsLib::ForcePause(PerChannelDataBlock *pChannels, const uint32 nChannelCnt) +{ + if (!ShouldRespond(COMMAND_FORCE_PAUSE)) + { + return; + } + + // Set gain to 0 + for (int channel_idx = 0; channel_idx < nChannelCnt; channel_idx++) + { + PerChannelDataBlock *channelData = &pChannels[channel_idx]; + channelData->channelStruct.panner.currentGainL32Q28 = 0; + } + + StateTransition(COMMAND_FORCE_PAUSE); +} + +void CSoftVolumeControlsLib::StartSoftPause(void *pChannelStruct) +{ + perChannelData *pChannelData = reinterpret_cast(pChannelStruct); + + m_isPaused = true; + + if (!pChannelData->isMuted) + { + // Ramp to zero + SetupPanner(&pChannelData->panner, 0, m_softPauseParams); + } +} + +void CSoftVolumeControlsLib::StartSoftPauseAllChannels(PerChannelDataBlock *pChannels, uint32 nChannelCnt) +{ + if (!ShouldRespond(COMMAND_PAUSE)) + { + return; + } + if (MUTE_BEFORE_RAMPUP == m_pauseState) + { + m_muteBeforeResumeParams.m_muteBeforeRampSamples = 0; + m_muteBeforeResumeParams.m_muteSamplesPending = m_muteBeforeResumeParams.m_muteBeforeRampSamples; + } + for (int channel_idx = 0; channel_idx < nChannelCnt; channel_idx++) + { + PerChannelDataBlock *channelData = &pChannels[channel_idx]; + StartSoftPause((void *)&(channelData->channelStruct)); + } + + StateTransition(COMMAND_PAUSE); // steady goes to ramping down +} + +void CSoftVolumeControlsLib::StartSoftResume(void *pChannelStruct) +{ + perChannelData *pChannelData = reinterpret_cast(pChannelStruct); + + m_isPaused = false; + // Ramp to target gain + if (!pChannelData->isMuted) + { + SetupPanner(&pChannelData->panner, pChannelData->chanGainQ28, m_softResumeParams); + } +} + +void CSoftVolumeControlsLib::StartSoftResumeAllChannels(PerChannelDataBlock *pChannels, const uint32 nChannelCnt) +{ + if (!ShouldRespond(COMMAND_RESUME)) + { + return; + } + if (PAUSE == m_pauseState) + { + m_muteBeforeResumeParams.m_muteBeforeRampSamples = + (uint32)((uint64)m_muteBeforeResumeParams.m_resumeWithDelayInMs * (uint64)m_sampleRate / 1000); + m_muteBeforeResumeParams.m_muteSamplesPending = m_muteBeforeResumeParams.m_muteBeforeRampSamples; + } + for (uint32_t channel_idx = 0; channel_idx < nChannelCnt; channel_idx++) + { + PerChannelDataBlock *channelData = &pChannels[channel_idx]; + StartSoftResume((void *)&(channelData->channelStruct)); + } + StateTransition(COMMAND_RESUME); +} + +void CSoftVolumeControlsLib::SetSampleRate(uint32_t oldSampleRate, uint32_t newSampleRate, void *pChannelStruct) +{ + m_sampleRate = newSampleRate; + + if (oldSampleRate != m_sampleRate) + { + // Need to recalculate the panner params for the new sampling rate + perChannelData *pChannelData = reinterpret_cast(pChannelStruct); + SvpannerStruct *pPanner = &pChannelData->panner; + + SoftSteppingParams params; + + params.rampingCurve = pPanner->rampingCurve; + // To avoid overflow, use 64 bit calculations here. + params.stepUs = (uint64)pPanner->step * 1000000 / oldSampleRate; + params.periodMs = (uint64)pPanner->sampleCounter * 1000 / oldSampleRate; + + SetupPanner(pPanner, pPanner->targetgainL32Q28, params); + } +} + +void CSoftVolumeControlsLib::GetSoftPauseParams(SoftSteppingParams *pSoftPauseParams) const +{ + *pSoftPauseParams = m_softPauseParams; +} + +void CSoftVolumeControlsLib::GetSoftResumeParams(SoftSteppingParams *pSoftResumeParams) const +{ + *pSoftResumeParams = m_softResumeParams; +} + +void CSoftVolumeControlsLib::GetSoftVolumeParams(SoftSteppingParams *pSoftVolumeParams) const +{ + *pSoftVolumeParams = m_softVolumeParams; +} + +void CSoftVolumeControlsLib::GetSoftMuteParams(SoftSteppingParams *pSoftMuteParams) const +{ + *pSoftMuteParams = m_softMuteParams; +} + +uint32 CSoftVolumeControlsLib::GetSampleRate(void) const +{ + return m_sampleRate; +} + +uint32 CSoftVolumeControlsLib::GetResumeWithDelayParam() const +{ + return m_muteBeforeResumeParams.m_resumeWithDelayInMs; +} + +void CSoftVolumeControlsLib::SetBytesPerSample(const uint32_t bytesPerSample) +{ + boolean isSupported = ((2 == bytesPerSample) || (4 == bytesPerSample)); + + if (isSupported) + { + m_bytesPerSample = bytesPerSample; + } +} + +void CSoftVolumeControlsLib::SetQFactor(const uint32 qFactor) +{ + boolean isSupported = ((15 == qFactor) || (27 == qFactor) || (31 == qFactor)); + + if (isSupported) + { + m_qFactor = qFactor; + } +} + +void CSoftVolumeControlsLib::SetupPanner(SvpannerStruct *panner, uint32 newGainL32Q28, const SoftSteppingParams ¶ms) +{ + // To avoid overflow, use 64 bit calculations here. This is achieved by having the sample rate be 64 bit. + uint64 sampleRate = m_sampleRate; + uint32 periodSamples = params.periodMs * sampleRate / 1000; + uint32 stepSamples = params.stepUs * sampleRate / 1000000; + panner->rampingCurve = params.rampingCurve; + + switch (params.rampingCurve) + { + case RAMP_LINEAR: + SoftPannerLinearSetup(panner, newGainL32Q28, periodSamples, stepSamples); + break; + case RAMP_EXP: + SoftPannerExpSetup(panner, newGainL32Q28, periodSamples, stepSamples); + break; + case RAMP_LOG: + SoftPannerLogSetup(panner, newGainL32Q28, periodSamples, stepSamples); + break; + case RAMP_FRACT_EXP: + SoftPannerFractExpSetup(panner, newGainL32Q28, periodSamples, stepSamples); + break; + } +} + +boolean CSoftVolumeControlsLib::isUnityGain(const void *pChannelStruct) const +{ + if (m_isPaused) + { + return false; + } + + const perChannelData *pChannelData = reinterpret_cast(pChannelStruct); + + if (pChannelData->isMuted) + { + return false; + } + + // Check if we are soft stepping + const SvpannerStruct *pPanner = &pChannelData->panner; + if (pPanner->targetgainL32Q28 != pPanner->currentGainL32Q28) + { + return false; + } + + // Check if the gain is unity + if (UNITY_L32_Q28 != pPanner->currentGainL32Q28) + { + return false; + } + + return true; +} + +void CSoftVolumeControlsLib::InitializePerChannelStruct(void *pChannelStruct) +{ + perChannelData *pChannelData = reinterpret_cast(pChannelStruct); + + pChannelData->chanGainQ28 = UNITY_L32_Q28; + pChannelData->isMuted = false; + SetupPanner(&pChannelData->panner, pChannelData->chanGainQ28, m_softVolumeParams); +} + +uint32 CSoftVolumeControlsLib::GetSizeOfPerChannelStruct(void) +{ + return sizeof(perChannelData); +} + +void CSoftVolumeControlsLib::GoToTargetGainImmediately(void *pChannelStruct) +{ + perChannelData *pChannelData = reinterpret_cast(pChannelStruct); + + SoftSteppingParams params; + params.periodMs = 0; + params.rampingCurve = RAMP_LINEAR; + params.stepUs = 0; + + SetupPanner(&pChannelData->panner, pChannelData->panner.targetgainL32Q28, params); +} + +void CSoftVolumeControlsLib::SoftPannerFractExpSetup(SvpannerStruct *panner, + uint32 newGainL32Q28, + uint32 rampSamples, + uint32 step) +{ + /* substitute panner struct values with shorter names */ + uint32 currentGainL32Q28 = panner->currentGainL32Q28; + uint32 sampleCounter = 0; + uint32 AL32Q28 = 0; + uint32 BL32Q28 = 0; + uint32 CL32Q31 = 0; + int32 deltaCL32Q31 = 0; + + if (step <= 0) + { + step = 1; + } + + /*----------------------------- if no ramp ------------------------------*/ + if (rampSamples <= 0) + { + currentGainL32Q28 = newGainL32Q28; + } /* end of if (rampSamples <= 0) */ + /*--------------- if there is ramp, then need some work here ------------*/ + else /* (rampSamples > 0) */ + { + if (currentGainL32Q28 < newGainL32Q28) + { + AL32Q28 = currentGainL32Q28; + BL32Q28 = newGainL32Q28 - currentGainL32Q28; + uint64 stepL64Q31 = ((uint64)(step)) << 31; + uint32 stepOverSamplesL32Q31 = (uint32)(stepL64Q31 / rampSamples); + deltaCL32Q31 = stepOverSamplesL32Q31; + CL32Q31 = stepOverSamplesL32Q31; + } + else + { + AL32Q28 = newGainL32Q28; + BL32Q28 = currentGainL32Q28 - newGainL32Q28; + uint64 stepL64Q31 = ((uint64)(step)) << 31; + int32 stepOverSamplesL32Q31 = (int32)(stepL64Q31 / rampSamples); + deltaCL32Q31 = -stepOverSamplesL32Q31; + CL32Q31 = (1u << 31) - stepOverSamplesL32Q31; + } + + sampleCounter = rampSamples; + } /* end of else (rampSamples > 0) */ + + /*------------- store new values back into panner struct ----------------*/ + + panner->step = step; + panner->stepResidue = step; + panner->coeffs.fract.AL32Q28 = AL32Q28; + panner->coeffs.fract.BL32Q28 = BL32Q28; + panner->coeffs.fract.CL32Q31 = CL32Q31; + panner->coeffs.fract.deltaCL32Q31 = deltaCL32Q31; + panner->targetgainL32Q28 = newGainL32Q28; // update target gain + panner->sampleCounter = sampleCounter; + panner->currentGainL32Q28 = currentGainL32Q28; + panner->index = 0; +} diff --git a/modules/processing/volume_control/lib/src/softvolumecontrols_island.cpp b/modules/processing/volume_control/lib/src/softvolumecontrols_island.cpp new file mode 100644 index 0000000..879e4c7 --- /dev/null +++ b/modules/processing/volume_control/lib/src/softvolumecontrols_island.cpp @@ -0,0 +1,2296 @@ +/* + * Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. + * SPDX-License-Identifier: BSD-3-Clause-Clear + */ +/*=========================================================================== + * FILE NAME: SoftVolumeControls.cpp + * DESCRIPTION: + * Volume and Soft stepping + *===========================================================================*/ + +#ifdef AVS_BUILD_SOS +#include "capi_cmn.h" +#endif + +#include "SoftVolumeControls.h" +#include "audio_basic_op.h" +#include "audio_divide_qx.h" +#include "audio_log10.h" +#include "audio_basic_op_ext.h" +#include +#include "audio_exp10.h" +#include "apply_gain.h" +#include "audio_clips.h" +#include "string.h" +#include "posal.h" + +#if ((defined __hexagon__) || (defined __qdsp6__)) +#include +#include +#include +// #include "OmmUtils.h" +#endif + +// Use to override AR_MSG with AR_MSG_ISLAND. Always include this after ar_msg.h +#ifdef AR_MSG_IN_ISLAND +#include "ar_msg_island_override.h" +#endif + +static uint32_t pow_1_75(uint32_t x); + +#if ((defined __hexagon__) || (defined __qdsp6__)) +extern "C" { +void SoftVolumeControlsExp2Fixed_asm(int32 input1L32Q26, int32 input2L32Q26, int32 *y1L32Q14, int32 *y2L32Q14); +} +#endif + +static const int16 log2table[] = { 0x7800, 0x782d, 0x785a, 0x7884, 0x78ae, 0x78d6, 0x78fe, 0x7924, + 0x794a, 0x796e, 0x7992, 0x79b4, 0x79d6, 0x79f8, 0x7a18, 0x7a38, + 0x7a57, 0x7a75, 0x7a93, 0x7ab1, 0x7acd, 0x7ae9, 0x7b05, 0x7b20, + 0x7b3b, 0x7b55, 0x7b6f, 0x7b88, 0x7ba1, 0x7bb9, 0x7bd1, 0x7be9 }; + +static const int32 THRESHOLD_NOT_DETECTED = -1; +extern "C" { +const int32 fractionalPowersOf2L32Q16[] = { + 65536, // 2^0 + 77936, // 2^0.25 + 92682, // 2^0.5 + 110218, // 2^0.75 +}; +} // extern C +static const uint32 UNITY_L32_Q28 = 1 << 28; + +#if ((defined __hexagon__) || (defined __qdsp6__)) +static boolean isAlignedTo8Byte(void *ptr) +{ + uint32 val = reinterpret_cast(ptr); + if (0 == (val & 0x7)) + { + return 1; + } + else + { + return 0; + } +} + +static void ApplySteadyGainLessThanOne16(void *pOutPtr, void *pInPtr, const uint32 gainQ28, uint32 samples) +{ + int16 *pInput = (int16 *)(pInPtr); + int16 *pOutput = (int16 *)(pOutPtr); + + while (!isAlignedTo8Byte(pOutput) && (samples > 0)) + { + int64 input = *pInput++; + int64 product = input * gainQ28; + product = s64_shr_s64_imm5_rnd(product, 28); + *pOutput++ = s16_saturate_s32(product); + samples--; + } + + union reg64bit + { + uint64 full; + struct + { + uint32 l; + uint32 h; + }; + }; + + reg64bit gain; + gain.l = gain.h = gainQ28; + + uint32 count = samples / 8; + uint32 remaining = samples % 8; + uint64 *pOutput8ByteAligned = (uint64 *)(pOutput); + int64 round = 0x0000080000000800; + + for (uint32 i = 0; i < count; i++) + { + reg64bit input_1, input_2, input_3, input_4, prod_1, prod_2, prod_3, prod_4; + + input_1.l = *pInput++; + input_1.h = *pInput++; + input_2.l = *pInput++; + input_2.h = *pInput++; + + input_3.l = *pInput++; + input_3.h = *pInput++; + input_4.l = *pInput++; + input_4.h = *pInput++; + + prod_1.full = Q6_P_vmpywehacc_PP_sat(round, gain.full, input_1.full); + prod_2.full = Q6_P_vmpywehacc_PP_sat(round, gain.full, input_2.full); + prod_3.full = Q6_P_vmpywehacc_PP_sat(round, gain.full, input_3.full); + prod_4.full = Q6_P_vmpywehacc_PP_sat(round, gain.full, input_4.full); + + reg64bit output_1, output_2; + output_1.l = Q6_R_vasrw_PI(prod_1.full, 28 - 16); + output_1.h = Q6_R_vasrw_PI(prod_2.full, 28 - 16); + output_2.l = Q6_R_vasrw_PI(prod_3.full, 28 - 16); + output_2.h = Q6_R_vasrw_PI(prod_4.full, 28 - 16); + + *pOutput8ByteAligned++ = output_1.full; + *pOutput8ByteAligned++ = output_2.full; + } + pOutput += (count * 8); + + for (uint32 i = 0; i < remaining; i++) + { + int64 input = *pInput++; + int64 product = input * gainQ28; + product = s64_shr_s64_imm5_rnd(product, 28); + *pOutput++ = s16_saturate_s32(product); + } +} +#endif + +static void ApplyLinearRamp16bitStepSize1(void *pOutPtr, void *pInPtr, SvpannerStruct *panner, uint32 samples) +{ + int64 currentGainL64Q59_1 = panner->coeffs.linear.currentGainL64Q59; + int64 currentGainL64Q59_2 = panner->coeffs.linear.currentGainL64Q59; + + uint32 currentGainL32Q28_1 = panner->currentGainL32Q28; + uint32 currentGainL32Q28_2 = panner->currentGainL32Q28; + uint32 targetgainL32Q28 = panner->targetgainL32Q28; + int64 deltaGainL64Q59 = panner->coeffs.linear.deltaL64Q59; + + int16 *pInput = (int16 *)(pInPtr); + int16 *pOutput = (int16 *)(pOutPtr); + + uint32 count = samples - 1; + + if (panner->newGainL32Q28 < (uint32)0x80000000) + { + // unroll the loop by 2 + while (count >= 2) + { + currentGainL64Q59_1 = currentGainL64Q59_2 + deltaGainL64Q59; + currentGainL64Q59_2 = currentGainL64Q59_1 + deltaGainL64Q59; + + int32 input = *pInput++; + int32 input2 = *pInput++; + /* The value of currentgain for the next iteration*/ + currentGainL32Q28_2 = currentGainL64Q59_1 >> (59 - 28); + + int64 product, product2; + + { + product = s64_mult_s32_s32(input, currentGainL32Q28_1); + product2 = s64_mult_s32_s32(input2, currentGainL32Q28_2); + } + + product = s64_shr_s64_imm5_rnd(product, 28); + product2 = s64_shr_s64_imm5_rnd(product2, 28); + + *pOutput++ = s16_saturate_s32(product); + *pOutput++ = s16_saturate_s32(product2); + + currentGainL32Q28_1 = currentGainL64Q59_2 >> (59 - 28); + + count -= 2; + } // while loop + } + else + { + // unroll the loop by 2 + while (count >= 2) + { + currentGainL64Q59_1 = currentGainL64Q59_2 + deltaGainL64Q59; + currentGainL64Q59_2 = currentGainL64Q59_1 + deltaGainL64Q59; + + int32 input = *pInput++; + int32 input2 = *pInput++; + /* The value of currentgain for the next iteration*/ + currentGainL32Q28_2 = currentGainL64Q59_1 >> (59 - 28); + + int64 product, product2; + + { + product = (int64)input * currentGainL32Q28_1; + product2 = (int64)input2 * currentGainL32Q28_2; + } + + product = s64_shr_s64_imm5_rnd(product, 28); + product2 = s64_shr_s64_imm5_rnd(product2, 28); + + *pOutput++ = s16_saturate_s32(product); + *pOutput++ = s16_saturate_s32(product2); + + currentGainL32Q28_1 = currentGainL64Q59_2 >> (59 - 28); + + count -= 2; + } // while loop + } + + count++; + + // process remaining samples + while (count--) + { + currentGainL64Q59_2 += deltaGainL64Q59; + if (currentGainL64Q59_2 < 0) + { + currentGainL64Q59_2 = targetgainL32Q28; + } + + int32 input = *pInput++; + int64 product; + + if ((currentGainL32Q28_1 < (uint32)0x80000000)) + { + product = s64_mult_s32_s32(input, currentGainL32Q28_1); + } + else + { + product = (int64)input * currentGainL32Q28_1; + } + + product = s64_shr_s64_imm5_rnd(product, 28); + *pOutput++ = s16_saturate_s32(product); + + /* The value of currentgain for the next iteration*/ + currentGainL32Q28_1 = currentGainL64Q59_2 >> (59 - 28); + } // for loop + + panner->currentGainL32Q28 = currentGainL32Q28_1; + panner->coeffs.linear.currentGainL64Q59 = currentGainL64Q59_2; +} + +static void ApplyLinearRamp32bitStepSize1(void *pOutPtr, void *pInPtr, SvpannerStruct *panner, uint32 samples) +{ + int64 currentGainL64Q59_1 = panner->coeffs.linear.currentGainL64Q59; + int64 currentGainL64Q59_2 = panner->coeffs.linear.currentGainL64Q59; + + uint32 currentGainL32Q28_1 = panner->currentGainL32Q28; + uint32 currentGainL32Q28_2 = panner->currentGainL32Q28; + + int64 deltaGainL64Q59 = panner->coeffs.linear.deltaL64Q59; + uint32 targetgainL32Q28 = panner->targetgainL32Q28; + int32 *pInput = (int32 *)(pInPtr); + int32 *pOutput = (int32 *)(pOutPtr); + + uint32 count = samples - 1; // count will be incremented next + + if (panner->newGainL32Q28 < (uint32)0x80000000) + { + // unroll the loop by 2 + while (count >= 2) + { + currentGainL64Q59_1 = currentGainL64Q59_2 + deltaGainL64Q59; + currentGainL64Q59_2 = currentGainL64Q59_1 + deltaGainL64Q59; + + int32 input = *pInput++; + int32 input2 = *pInput++; + /* The value of currentgain for the next iteration*/ + currentGainL32Q28_2 = currentGainL64Q59_1 >> (59 - 28); + int64 product; + int64 product2; + + { + product = s64_mult_s32_s32(input, currentGainL32Q28_1); + product2 = s64_mult_s32_s32(input2, currentGainL32Q28_2); + } + + product = s64_shr_s64_imm5_rnd(product, 28); + product2 = s64_shr_s64_imm5_rnd(product2, 28); + + *pOutput++ = s32_saturate_s64(product); + *pOutput++ = s32_saturate_s64(product2); + + currentGainL32Q28_1 = currentGainL64Q59_2 >> (59 - 28); + + count -= 2; + } // for loop + } + else + { + // unroll the loop by 2 + while (count >= 2) + { + currentGainL64Q59_1 = currentGainL64Q59_2 + deltaGainL64Q59; + currentGainL64Q59_2 = currentGainL64Q59_1 + deltaGainL64Q59; + + int32 input = *pInput++; + int32 input2 = *pInput++; + /* The value of currentgain for the next iteration*/ + currentGainL32Q28_2 = currentGainL64Q59_1 >> (59 - 28); + int64 product; + int64 product2; + + { + product = (int64)input * currentGainL32Q28_1; + product2 = (int64)input2 * currentGainL32Q28_2; + } + + product = s64_shr_s64_imm5_rnd(product, 28); + product2 = s64_shr_s64_imm5_rnd(product2, 28); + + *pOutput++ = s32_saturate_s64(product); + *pOutput++ = s32_saturate_s64(product2); + + currentGainL32Q28_1 = currentGainL64Q59_2 >> (59 - 28); + + count -= 2; + } // for loop + } + + count++; + + // process remaining samples + while (count--) + { + currentGainL64Q59_2 += deltaGainL64Q59; + if (currentGainL64Q59_2 < 0) + { + currentGainL64Q59_2 = targetgainL32Q28; + } + + int32 input = *pInput++; + int64 product; + + if ((currentGainL32Q28_1 < (uint32)0x80000000)) + { + product = s64_mult_s32_s32(input, currentGainL32Q28_1); + } + else + { + + product = (int64)input * currentGainL32Q28_1; + } + + product = s64_shr_s64_imm5_rnd(product, 28); + *pOutput++ = s32_saturate_s64(product); + + /* The value of currentgain for the next iteration*/ + currentGainL32Q28_1 = currentGainL64Q59_2 >> (59 - 28); + } // for loop + + panner->currentGainL32Q28 = currentGainL32Q28_1; + panner->coeffs.linear.currentGainL64Q59 = currentGainL64Q59_2; +} + +/*============================================================================= +FUNCTION audpp_sftvlmctrl_logbase2_fixed + +DESCRIPTION Calculates log2(input). Works only for positive numbers. + +INPUTS +input: input in integer format. + +OUTPUTS + +DEPENDENCIES None + +RETURN VALUE +log2(input), in Q10 format + +SIDE EFFECTS +===============================================================================*/ +static int32 SoftVolumeControlsLogBase2Fixed(int32 input) +{ + int16 shiftNorm; + int32 normalizedInput, exponentL32Q10, logNormInputL32Q10, logInputL32Q10; + + /* Normalize Input */ + shiftNorm = s16_norm_s32(input); /* Final normalization shift factor */ + normalizedInput = s32_shl_s32_sat(input, shiftNorm); /* normalize input */ + + /* Calculate log2(normalizedInput) */ + { + int16 intermediateTerm, index; + intermediateTerm = normalizedInput >> (31 - 6); // This will give a number in the range (32,63) + index = intermediateTerm - 32; // This will give a number in the range (0,31) + logNormInputL32Q10 = log2table[index]; + } + + /* get the exponent to subtract for normalization adjustment */ + exponentL32Q10 = shiftNorm << 10; + + /* Adjust for normalization */ + logInputL32Q10 = logNormInputL32Q10 - exponentL32Q10; + + return logInputL32Q10; +} + +static void SoftVolumeControlsLogBase2Fixed(int32 input1, int32 input2, int32 *output1, int32 *output2) +{ + int16 shiftNorm1, shiftNorm2; + int32 normalizedInput_1, exponentL32Q10_1, logNormInputL32Q10_1; + int32 normalizedInput_2, exponentL32Q10_2, logNormInputL32Q10_2; + + /* Normalize Input */ + shiftNorm1 = s16_norm_s32(input1); /* Final normalization shift factor */ + shiftNorm2 = s16_norm_s32(input2); /* Final normalization shift factor */ + normalizedInput_1 = s32_shl_s32_sat(input1, shiftNorm1); /* normalize input */ + normalizedInput_2 = s32_shl_s32_sat(input2, shiftNorm2); /* normalize input */ + + /* Calculate log2(normalizedInput) */ + { + int16 intermediateTerm_1, index_1; + int16 intermediateTerm_2, index_2; + intermediateTerm_1 = normalizedInput_1 >> (31 - 6); // This will give a number in the range (32,63) + intermediateTerm_2 = normalizedInput_2 >> (31 - 6); // This will give a number in the range (32,63) + index_1 = intermediateTerm_1 - 32; // This will give a number in the range (0,31) + index_2 = intermediateTerm_2 - 32; // This will give a number in the range (0,31) + logNormInputL32Q10_1 = log2table[index_1]; + logNormInputL32Q10_2 = log2table[index_2]; + } + + /* get the exponent to subtract for normalization adjustment */ + exponentL32Q10_1 = shiftNorm1 << 10; + exponentL32Q10_2 = shiftNorm2 << 10; + /* Adjust for normalization */ + *output1 = logNormInputL32Q10_1 - exponentL32Q10_1; + *output2 = logNormInputL32Q10_2 - exponentL32Q10_2; +} + +/*============================================================================= +FUNCTION audpp_sftvlmctrl_exp2_fixed + +DESCRIPTION Calculates 2^input. Works only for positive numbers. + +INPUTS +inputL32Q26: input in Q26 format. Input must be in the range (0,32) + +OUTPUTS + +DEPENDENCIES None + +RETURN VALUE +2^input, in Q14 format + +SIDE EFFECTS +===============================================================================*/ +int32 SoftVolumeControlsExp2FixedSingle(int32 inputL32Q26) +{ + // Calculate 2^x for the lower 24 bits + // int64 yL64Q26 = 0; + + /* + Uses the Taylor series expansion around 0.125 to calculate + 2^x where 0 <= x < 0.25 + + 2^x ~= 2^0.125 + 2^0.125*ln(2)*(x-0.125) + 2^0.125*(ln(2))^2*(x-0.125)^2 + */ + + const int32 A1L32Q26 = 67116628; + const int32 B1L32Q16 = 45245; + const int32 C1L32Q16 = 17168; + // x = Lower 24 bits of the input. + const uint32 mask_0_25 = ((1 << 24) - 1); + int32 xL32Q26 = (inputL32Q26 & mask_0_25); + + // A1 + B1x + C1x^2 + // int64 product64 = A1L32Q26; + + int64 productL64Q42_1 = s64_mult_s32_s32(B1L32Q16, xL32Q26); + + // int64 productL64Q42_1 = B1L32Q16 * xL64Q26; + int64 x2L64Q26 = s64_mult_s32_s32(xL32Q26, xL32Q26) >> 26; + // int64 x2L64Q26 = (xL64Q26 * xL64Q26)>>26; + + int64 productL64Q42_2 = (x2L64Q26 * C1L32Q16); + // int64 productL64Q42_2 = s64_mult_s32_s32(x2L32Q26 , C1L32Q16); + + int64 yL64Q26 = A1L32Q26 + ((productL64Q42_1 + productL64Q42_2) >> 16); + + // Now handle the next 2 bits + + const uint32 mask_frac_Q2 = ((1 << 2) - 1); + + // x = Fractional part of the input in Q2 + int32 xL32Q2 = (inputL32Q26 >> (26 - 2)) & mask_frac_Q2; + + // y = y * x + yL64Q26 = ((yL64Q26 * fractionalPowersOf2L32Q16[xL32Q2]) >> 16); + + // Now handle the integer part, and convert to Q14 + int32 yL32Q14 = 0; + + // y = y * 2^(integer part of the input) + int32 shiftVal = 26 - 14 - (inputL32Q26 >> 26); // 26 - 14 is to convert to Q14 + yL32Q14 = (yL64Q26 >> shiftVal); + + return yL32Q14; +} + +void SoftVolumeControlsExp2Fixed(int32 input1L32Q26, int32 input2L32Q26, int32 *y1L32Q14, int32 *y2L32Q14) +{ + // Calculate 2^x for the lower 24 bits + + /* + Uses the Taylor series expansion around 0.125 to calculate + 2^x where 0 <= x < 0.25 + + 2^x ~= 2^0.125 + 2^0.125*ln(2)*(x-0.125) + 2^0.125*(ln(2))^2*(x-0.125)^2 + */ + + const int32 A1L32Q26 = 67116628; + const int32 B1L32Q16 = 45245; + const int32 C1L32Q16 = 17168; + + // x = Lower 24 bits of the input. + const uint32 mask_0_25 = ((1 << 24) - 1); + + int32 x1L32Q26 = (input1L32Q26 & mask_0_25); + int32 x2L32Q26 = (input2L32Q26 & mask_0_25); + + int64 product1L64Q42_1 = s64_mult_s32_s32(B1L32Q16, x1L32Q26); + int64 product1L64Q42_2 = s64_mult_s32_s32(B1L32Q16, x2L32Q26); + + int64 x2L64Q26_1 = s64_mult_s32_s32(x1L32Q26, x1L32Q26) >> 26; + int64 x2L64Q26_2 = s64_mult_s32_s32(x2L32Q26, x2L32Q26) >> 26; + + int64 product2L64Q42_1 = (x2L64Q26_1 * C1L32Q16); + int64 product2L64Q42_2 = (x2L64Q26_2 * C1L32Q16); + + int64 y1L64Q26 = A1L32Q26 + ((product1L64Q42_1 + product2L64Q42_1) >> 16); + int64 y2L64Q26 = A1L32Q26 + ((product1L64Q42_2 + product2L64Q42_2) >> 16); + + // Now handle the next 2 bits + + const uint32 mask_frac_Q2 = ((1 << 2) - 1); + + // x = Fractional part of the input in Q2 + int32 x1L32Q2 = (input1L32Q26 >> (26 - 2)) & mask_frac_Q2; + int32 x2L32Q2 = (input2L32Q26 >> (26 - 2)) & mask_frac_Q2; + const int32 fractionalPowersOf2L32Q16[] = { + 65536, // 2^0 + 77936, // 2^0.25 + 92682, // 2^0.5 + 110218, // 2^0.75 + }; + + // y = y * x + y1L64Q26 = ((y1L64Q26 * fractionalPowersOf2L32Q16[x1L32Q2]) >> 16); + y2L64Q26 = ((y2L64Q26 * fractionalPowersOf2L32Q16[x2L32Q2]) >> 16); + + // Now handle the integer part, and convert to Q14 + + // y = y * 2^(integer part of the input) + int32 shiftVal1 = 26 - 14 - (input1L32Q26 >> 26); // 26 - 14 is to convert to Q14 + int32 shiftVal2 = 26 - 14 - (input2L32Q26 >> 26); // 26 - 14 is to convert to Q14 + *y1L32Q14 = (y1L64Q26 >> shiftVal1); + *y2L32Q14 = (y2L64Q26 >> shiftVal2); + + return; +} + +/*============================================================================= +FUNCTION CSoftVolumeControlsLib::apply_linear_ramp + +DESCRIPTION increase gain(Q12) in a linear curve and apply to the input(Q15) and +save to output (Q15) +OUTPUTS + +DEPENDENCIES None + +RETURN VALUE None + +SIDE EFFECTS +===============================================================================*/ +void CSoftVolumeControlsLib::ApplyLinearRamp(void *pOutPtr, void *pInPtr, SvpannerStruct *panner, uint32 samples) +{ + uint32 temp, deltaStep; + int64 currentGainL64Q59 = panner->coeffs.linear.currentGainL64Q59; + uint32 currentGainL32Q28 = panner->currentGainL32Q28; + uint32 i; + int64 deltaGainL64Q59 = panner->coeffs.linear.deltaL64Q59; + uint32 pannerStep = panner->step; + uint32 pannerStepResidue = panner->stepResidue; + uint32 pannerIndex = panner->index; + + if (panner->step <= 1) + { + if (m_bytesPerSample == 2) + { + ApplyLinearRamp16bitStepSize1(pOutPtr, pInPtr, panner, samples); + } // if + else // (m_bytesPerSample == 4) + { + ApplyLinearRamp32bitStepSize1(pOutPtr, pInPtr, panner, samples); + } + } + else // (step > 1) + { + for (i = samples; i > 0;) + { + /* deltastep is the number of samples for which the current value of gain is to be applied*/ + temp = pannerStep - pannerStepResidue; + deltaStep = (i < temp) ? i : temp; + pannerIndex = pannerIndex + deltaStep; + + /* apply the gain to deltastep number of samples*/ + ApplySteadyGain(pOutPtr, pInPtr, currentGainL32Q28, deltaStep); + IncrementPointer(&pInPtr, deltaStep); + IncrementPointer(&pOutPtr, deltaStep); + + /* number of samples left in the current frame */ + i -= deltaStep; + + if (deltaStep < temp) /* not enough samples left in the current frame apply gain to step size of samples*/ + { + pannerStepResidue = pannerStepResidue + deltaStep; + } + else /* enough samples left in the current frame apply gain to step size of samples*/ + { + pannerStepResidue = 0; + currentGainL64Q59 += deltaGainL64Q59; + if (currentGainL64Q59 < 0) + { + currentGainL64Q59 = 0; + } + currentGainL32Q28 = currentGainL64Q59 >> (59 - 28); + } + } + panner->currentGainL32Q28 = currentGainL32Q28; + panner->coeffs.linear.currentGainL64Q59 = currentGainL64Q59; + panner->step = pannerStep; + panner->stepResidue = pannerStepResidue; + panner->index = pannerIndex; + } +} + +static void ApplyLogRamp16bitStep1(void *pOutPtr, void *pInPtr, SvpannerStruct *panner, uint32 samples) +{ + int32 AL32Q26 = panner->coeffs.log.AL32Q26; + int32 BL32Q26 = panner->coeffs.log.BL32Q26; + int32 CL32Q16_1 = panner->coeffs.log.CL32Q16; + int32 CL32Q16_2 = panner->coeffs.log.CL32Q16; + int32 deltaCL32Q16 = panner->coeffs.log.deltaCL32Q16; + uint32 step = panner->step; + uint32 stepResidue = panner->stepResidue; + uint32 currentGainL32Q28 = panner->currentGainL32Q28; + + int16 *pInput = (int16 *)(pInPtr); + int16 *pOutput = (int16 *)(pOutPtr); + + if (0 != stepResidue) + { + + int32 input = *pInput++; + int64 product; + + // mpy works for both signed or unsigned, so as a work around when gain >= 0x80000000 we are using + // standard multiplication + if ((currentGainL32Q28 < (uint32)0x80000000)) + { + product = s64_mult_s32_s32(input, currentGainL32Q28); + } + else + { + + product = (int64)input * currentGainL32Q28; + } + + product = s64_shr_s64_imm5_rnd(product, 28); + *pOutput++ = s16_saturate_s32(product); + + stepResidue = 0; + samples--; + } + + if (panner->newGainL32Q28 < (uint32)0x80000000) + { + // loop unroll by 2 + while (samples >= 2) + { + + // Calculate the new gain + CL32Q16_1 = CL32Q16_2 + deltaCL32Q16; + CL32Q16_2 = CL32Q16_1 + deltaCL32Q16; + + int32 logL32Q10_1, logL32Q10_2; + SoftVolumeControlsLogBase2Fixed(CL32Q16_1, CL32Q16_2, &logL32Q10_1, &logL32Q10_2); + + // Since the log function returns the log assuming integer argument, + // need to adjust for the input being Q16 + logL32Q10_1 -= (16 << 10); + logL32Q10_2 -= (16 << 10); + + int32 logL32Q14_1 = logL32Q10_1 << (14 - 10); // The range will be within (0,5), so no overflow will happen + int32 logL32Q14_2 = logL32Q10_2 << (14 - 10); // The range will be within (0,5), so no overflow will happen + + int64 productL64Q40_1 = s64_mult_s32_s32(AL32Q26, logL32Q14_1); // both are signed + int64 productL64Q40_2 = s64_mult_s32_s32(AL32Q26, logL32Q14_2); + + int64 productL64Q26_1 = (productL64Q40_1 >> (40 - 26)); + int64 productL64Q26_2 = (productL64Q40_2 >> (40 - 26)); + + int32 productL32Q26_1 = s32_saturate_s64(productL64Q26_1); + int32 productL32Q26_2 = s32_saturate_s64(productL64Q26_2); + + int32 gainL32Q26_1 = BL32Q26 + productL32Q26_1; + + if (gainL32Q26_1 > 0) + { + currentGainL32Q28 = gainL32Q26_1 << (28 - 26); + } + else + { + currentGainL32Q28 = 0; + } + + int32 gainL32Q26_2 = BL32Q26 + productL32Q26_2; + int32 input1 = *pInput++; + int32 input2 = *pInput++; + int64 product1; + if ((currentGainL32Q28 < (uint32)0x80000000)) + { + product1 = s64_mult_s32_s32(input1, currentGainL32Q28); + } + else + { + product1 = (int64)input1 * currentGainL32Q28; + } + + if (gainL32Q26_2 > 0) + { + currentGainL32Q28 = gainL32Q26_2 << (28 - 26); + } + else + { + currentGainL32Q28 = 0; + } + + int64 product2; + + product2 = s64_mult_s32_s32(input2, currentGainL32Q28); + + product1 = s64_shr_s64_imm5_rnd(product1, 28); + product2 = s64_shr_s64_imm5_rnd(product2, 28); + + *pOutput++ = s16_saturate_s32(product1); + *pOutput++ = s16_saturate_s32(product2); + + samples -= 2; + } + } + else + { + // loop unroll by 2 + while (samples >= 2) + { + // Calculate the new gain + CL32Q16_1 = CL32Q16_2 + deltaCL32Q16; + CL32Q16_2 = CL32Q16_1 + deltaCL32Q16; + + int32 logL32Q10_1, logL32Q10_2; + SoftVolumeControlsLogBase2Fixed(CL32Q16_1, CL32Q16_2, &logL32Q10_1, &logL32Q10_2); + + // Since the log function returns the log assuming integer argument, + // need to adjust for the input being Q16 + logL32Q10_1 -= (16 << 10); + logL32Q10_2 -= (16 << 10); + + int32 logL32Q14_1 = logL32Q10_1 << (14 - 10); // The range will be within (0,5), so no overflow will happen + int32 logL32Q14_2 = logL32Q10_2 << (14 - 10); // The range will be within (0,5), so no overflow will happen + + int64 productL64Q40_1 = s64_mult_s32_s32(AL32Q26, logL32Q14_1); // both are signed + int64 productL64Q40_2 = s64_mult_s32_s32(AL32Q26, logL32Q14_2); + + int64 productL64Q26_1 = (productL64Q40_1 >> (40 - 26)); + int64 productL64Q26_2 = (productL64Q40_2 >> (40 - 26)); + + int32 productL32Q26_1 = s32_saturate_s64(productL64Q26_1); + int32 productL32Q26_2 = s32_saturate_s64(productL64Q26_2); + + int32 gainL32Q26_1 = BL32Q26 + productL32Q26_1; + + if (gainL32Q26_1 > 0) + { + currentGainL32Q28 = gainL32Q26_1 << (28 - 26); + } + else + { + currentGainL32Q28 = 0; + } + + int32 gainL32Q26_2 = BL32Q26 + productL32Q26_2; + int32 input1 = *pInput++; + int32 input2 = *pInput++; + int64 product1; + + product1 = (int64)input1 * currentGainL32Q28; + + if (gainL32Q26_2 > 0) + { + currentGainL32Q28 = gainL32Q26_2 << (28 - 26); + } + else + { + currentGainL32Q28 = 0; + } + + int64 product2; + + product2 = (int64)input2 * currentGainL32Q28; + + product1 = s64_shr_s64_imm5_rnd(product1, 28); + product2 = s64_shr_s64_imm5_rnd(product2, 28); + *pOutput++ = s16_saturate_s32(product1); + *pOutput++ = s16_saturate_s32(product2); + + samples -= 2; + } + } + + // process remaining samples + while (samples--) + { + + // Calculate the new gain + CL32Q16_2 += deltaCL32Q16; + + int32 logL32Q10 = SoftVolumeControlsLogBase2Fixed(CL32Q16_2); + // Since the log function returns the log assuming integer argument, + // need to adjust for the input being Q16 + logL32Q10 -= (16 << 10); + + int32 logL32Q14 = logL32Q10 << (14 - 10); // The range will be within (0,5), so no overflow will happen + int64 productL64Q40 = s64_mult_s32_s32(AL32Q26, logL32Q14); + int64 productL64Q26 = (productL64Q40 >> (40 - 26)); + + int32 productL32Q26 = s32_saturate_s64(productL64Q26); + + int32 gainL32Q26 = BL32Q26 + productL32Q26; + + if (gainL32Q26 > 0) + { + currentGainL32Q28 = gainL32Q26 << (28 - 26); + } + else + { + currentGainL32Q28 = 0; + } + + int32 input = *pInput++; + + int64 product; + if ((currentGainL32Q28 < (uint32)0x80000000)) + { + product = s64_mult_s32_s32(input, currentGainL32Q28); + } + else + { + product = (int64)input * currentGainL32Q28; + } + + product = s64_shr_s64_imm5_rnd(product, 28); + *pOutput++ = s16_saturate_s32(product); + } + + panner->coeffs.log.AL32Q26 = AL32Q26; + panner->coeffs.log.BL32Q26 = BL32Q26; + panner->coeffs.log.CL32Q16 = CL32Q16_2; + panner->coeffs.log.deltaCL32Q16 = deltaCL32Q16; + panner->step = step; + panner->stepResidue = stepResidue; + panner->currentGainL32Q28 = currentGainL32Q28; +} + +static void ApplyLogRamp32bitStep1(void *pOutPtr, void *pInPtr, SvpannerStruct *panner, uint32 samples) +{ + + int32 AL32Q26 = panner->coeffs.log.AL32Q26; + int32 BL32Q26 = panner->coeffs.log.BL32Q26; + int32 CL32Q16_1 = panner->coeffs.log.CL32Q16; + int32 CL32Q16_2 = panner->coeffs.log.CL32Q16; + int32 deltaCL32Q16 = panner->coeffs.log.deltaCL32Q16; + uint32 step = panner->step; + uint32 stepResidue = panner->stepResidue; + uint32 currentGainL32Q28 = panner->currentGainL32Q28; + + int32 *pInput = (int32 *)(pInPtr); + int32 *pOutput = (int32 *)(pOutPtr); + + if (0 != stepResidue) + { + + int32 input = *pInput++; + int64 product; + if ((currentGainL32Q28 < (uint32)0x80000000)) + { + product = s64_mult_s32_s32(input, currentGainL32Q28); + } + else + { + product = (int64)input * currentGainL32Q28; + } + + product = s64_shr_s64_imm5_rnd(product, 28); + *pOutput++ = s32_saturate_s64(product); + + stepResidue = 0; + samples--; + } + + // loop unroll by 2 + while (samples >= 2) + { + // Calculate the new gain + CL32Q16_1 = CL32Q16_2 + deltaCL32Q16; + CL32Q16_2 = CL32Q16_1 + deltaCL32Q16; + + int32 logL32Q10_1, logL32Q10_2; + SoftVolumeControlsLogBase2Fixed(CL32Q16_1, CL32Q16_2, &logL32Q10_1, &logL32Q10_2); + + // Since the log function returns the log assuming integer argument, + // need to adjust for the input being Q16 + logL32Q10_1 -= (16 << 10); + logL32Q10_2 -= (16 << 10); + + int32 logL32Q14_1 = logL32Q10_1 << (14 - 10); // The range will be within (0,5), so no overflow will happen + int32 logL32Q14_2 = logL32Q10_2 << (14 - 10); // The range will be within (0,5), so no overflow will happen + + int64 productL64Q40_1 = s64_mult_s32_s32(AL32Q26, logL32Q14_1); + int64 productL64Q40_2 = s64_mult_s32_s32(AL32Q26, logL32Q14_2); + + int64 productL64Q26_1 = (productL64Q40_1 >> (40 - 26)); + int64 productL64Q26_2 = (productL64Q40_2 >> (40 - 26)); + + int32 productL32Q26_1 = s32_saturate_s64(productL64Q26_1); + int32 productL32Q26_2 = s32_saturate_s64(productL64Q26_2); + + int32 gainL32Q26_1 = BL32Q26 + productL32Q26_1; + + if (gainL32Q26_1 > 0) + { + currentGainL32Q28 = gainL32Q26_1 << (28 - 26); + } + else + { + currentGainL32Q28 = 0; + } + + int32 gainL32Q26_2 = BL32Q26 + productL32Q26_2; + int32 input1 = *pInput++; + int32 input2 = *pInput++; + int64 product1; + if ((currentGainL32Q28 < (uint32)0x80000000)) + { + product1 = s64_mult_s32_s32(input1, currentGainL32Q28); + } + else + { + product1 = (int64)input1 * currentGainL32Q28; + } + + if (gainL32Q26_2 > 0) + { + currentGainL32Q28 = gainL32Q26_2 << (28 - 26); + } + else + { + currentGainL32Q28 = 0; + } + + int64 product2; + if ((currentGainL32Q28 < (uint32)0x80000000)) + { + product2 = s64_mult_s32_s32(input2, currentGainL32Q28); + } + else + { + product2 = (int64)input2 * currentGainL32Q28; + } + + product1 = s64_shr_s64_imm5_rnd(product1, 28); + product2 = s64_shr_s64_imm5_rnd(product2, 28); + *pOutput++ = s32_saturate_s64(product1); + *pOutput++ = s32_saturate_s64(product2); + + samples -= 2; + } + + // process remaining samples + while (samples--) + { + + // Calculate the new gain + CL32Q16_2 += deltaCL32Q16; + + int32 logL32Q10 = SoftVolumeControlsLogBase2Fixed(CL32Q16_2); + // Since the log function returns the log assuming integer argument, + // need to adjust for the input being Q16 + logL32Q10 -= (16 << 10); + + int32 logL32Q14 = logL32Q10 << (14 - 10); // The range will be within (0,5), so no overflow will happen + int64 productL64Q40 = s64_mult_s32_s32(AL32Q26, logL32Q14); + int64 productL64Q26 = (productL64Q40 >> (40 - 26)); + + int32 productL32Q26 = s32_saturate_s64(productL64Q26); + + int32 gainL32Q26 = BL32Q26 + productL32Q26; + + if (gainL32Q26 > 0) + { + currentGainL32Q28 = gainL32Q26 << (28 - 26); + } + else + { + currentGainL32Q28 = 0; + } + + int32 input = *pInput++; + int64 product; + if ((currentGainL32Q28 < (uint32)0x80000000)) + { + product = s64_mult_s32_s32(input, currentGainL32Q28); + } + else + { + product = (int64)input * currentGainL32Q28; + } + + product = s64_shr_s64_imm5_rnd(product, 28); + *pOutput++ = s32_saturate_s64(product); + } + + panner->coeffs.log.AL32Q26 = AL32Q26; + panner->coeffs.log.BL32Q26 = BL32Q26; + panner->coeffs.log.CL32Q16 = CL32Q16_2; + panner->coeffs.log.deltaCL32Q16 = deltaCL32Q16; + panner->step = step; + panner->stepResidue = stepResidue; + panner->currentGainL32Q28 = currentGainL32Q28; +} + +/*============================================================================= +FUNCTION CSoftVolumeControlsLib::ApplyLogRamp + +DESCRIPTION increase gain(Q12) in a logarithmic curve and apply to the input(Q15) and +save to output (Q15) +OUTPUTS + +DEPENDENCIES None + +RETURN VALUE None + +SIDE EFFECTS +===============================================================================*/ + +void CSoftVolumeControlsLib::ApplyLogRamp(void *pOutPtr, void *pInPtr, SvpannerStruct *panner, uint32 samples) +{ + uint32 i; + int32 AL32Q26 = panner->coeffs.log.AL32Q26; + int32 BL32Q26 = panner->coeffs.log.BL32Q26; + int32 CL32Q16 = panner->coeffs.log.CL32Q16; + int32 deltaCL32Q16 = panner->coeffs.log.deltaCL32Q16; + uint32 step = panner->step; + uint32 stepResidue = panner->stepResidue; + uint32 currentGainL32Q28 = panner->currentGainL32Q28; + + if (step <= 1) + { + if (m_bytesPerSample == 2) + { + ApplyLogRamp16bitStep1(pOutPtr, pInPtr, panner, samples); + + } // if + else // (m_bytesPerSample == 4) + { + ApplyLogRamp32bitStep1(pOutPtr, pInPtr, panner, samples); + } + } + else // step > 1 + { + for (i = samples; i > 0;) + { + if (0 == stepResidue) + { + // Calculate the new gain + CL32Q16 += deltaCL32Q16; + + int32 logL32Q10 = SoftVolumeControlsLogBase2Fixed(CL32Q16); + // Since the log function returns the log assuming integer argument, + // need to adjust for the input being Q16 + logL32Q10 -= (16 << 10); + + int32 logL32Q14 = logL32Q10 << (14 - 10); // The range will be within (0,5), so no overflow will happen + int64 productL64Q40 = ((int64)(AL32Q26)) * logL32Q14; + int64 productL64Q26 = (productL64Q40 >> (40 - 26)); + + int32 productL32Q26 = s32_saturate_s64(productL64Q26); + + int32 gainL32Q26 = BL32Q26 + productL32Q26; + if (gainL32Q26 > 0) + { + currentGainL32Q28 = gainL32Q26 << (28 - 26); + } + else + { + currentGainL32Q28 = 0; + } + + stepResidue = step; + } + + uint32 deltaStep = (i < stepResidue) ? i : stepResidue; + + ApplySteadyGain(pOutPtr, pInPtr, currentGainL32Q28, deltaStep); + + IncrementPointer(&pInPtr, deltaStep); + IncrementPointer(&pOutPtr, deltaStep); + + i -= deltaStep; + stepResidue -= deltaStep; + } + panner->coeffs.log.AL32Q26 = AL32Q26; + panner->coeffs.log.BL32Q26 = BL32Q26; + panner->coeffs.log.CL32Q16 = CL32Q16; + panner->coeffs.log.deltaCL32Q16 = deltaCL32Q16; + panner->step = step; + panner->stepResidue = stepResidue; + panner->currentGainL32Q28 = currentGainL32Q28; + } +} + +uint32 CSoftVolumeControlsLib::GetBytesPerSample(void) const +{ + return m_bytesPerSample; +} + +static void ApplyExpRamp16bitStep1(void *pOutPtr, void *pInPtr, SvpannerStruct *panner, uint32 samples) +{ + int32 AL32Q26 = panner->coeffs.exp.AL32Q26; + int32 BL32Q26 = panner->coeffs.exp.BL32Q26; + + int32 CL32Q26_1 = panner->coeffs.exp.CL32Q26; + int32 CL32Q26_2 = panner->coeffs.exp.CL32Q26; + + int32 deltaCL32Q26 = panner->coeffs.exp.deltaCL32Q26; + uint32 step = panner->step; + uint32 stepResidue = panner->stepResidue; + uint32 currentGainL32Q28 = panner->currentGainL32Q28; + + int16 *pInput = (int16 *)(pInPtr); + int16 *pOutput = (int16 *)(pOutPtr); + + if (0 != stepResidue) + { + + int32 input = *pInput++; + + int64 product; + + if ((currentGainL32Q28 < (uint32)0x80000000)) + { + product = s64_mult_s32_s32(input, currentGainL32Q28); + } + else + { + product = (int64)input * currentGainL32Q28; + } + + product = s64_shr_s64_imm5_rnd(product, 28); + *pOutput++ = s16_saturate_s32(product); + + stepResidue = 0; + samples--; + } + + if (panner->newGainL32Q28 < (uint32)0x80000000) + { + // loop unrolled by 2 + while (samples >= 2) + { + // Calculate the new gain + CL32Q26_1 = CL32Q26_2 + deltaCL32Q26; + CL32Q26_2 = CL32Q26_1 + deltaCL32Q26; + + int32 expL32Q14_1, expL32Q14_2; + +#if ((defined __hexagon__) || (defined __qdsp6__)) + SoftVolumeControlsExp2Fixed_asm(CL32Q26_1, CL32Q26_2, &expL32Q14_1, &expL32Q14_2); +#else + SoftVolumeControlsExp2Fixed(CL32Q26_1, CL32Q26_2, &expL32Q14_1, &expL32Q14_2); +#endif + + int64 product1L64Q40 = s64_mult_s32_s32(AL32Q26, expL32Q14_1); + int64 product2L64Q40 = s64_mult_s32_s32(AL32Q26, expL32Q14_2); + int64 product1L64Q26 = (product1L64Q40 >> (40 - 26)); + int64 product2L64Q26 = (product2L64Q40 >> (40 - 26)); + + int32 product1L32Q26 = s32_saturate_s64(product1L64Q26); + int32 product2L32Q26 = s32_saturate_s64(product2L64Q26); + + int32 gainL32Q26 = BL32Q26 + product1L32Q26; + int32 gain2L32Q26 = BL32Q26 + product2L32Q26; + + if (gainL32Q26 > 0) + { + currentGainL32Q28 = gainL32Q26 << (28 - 26); + } + else + { + currentGainL32Q28 = 0; + } + + int32 input = *pInput++; + int32 input2 = *pInput++; + + int64 product; + + product = s64_mult_s32_s32(input, currentGainL32Q28); + + if (gain2L32Q26 > 0) + { + currentGainL32Q28 = gain2L32Q26 << (28 - 26); + } + else + { + currentGainL32Q28 = 0; + } + int64 product2; + + product2 = s64_mult_s32_s32(input2, currentGainL32Q28); + + product = s64_shr_s64_imm5_rnd(product, 28); + product2 = s64_shr_s64_imm5_rnd(product2, 28); + + *pOutput++ = s16_saturate_s32(product); + *pOutput++ = s16_saturate_s32(product2); + samples -= 2; + } + } + else + { + // loop unrolled by 2 + while (samples >= 2) + { + // Calculate the new gain + CL32Q26_1 = CL32Q26_2 + deltaCL32Q26; + CL32Q26_2 = CL32Q26_1 + deltaCL32Q26; + + int32 expL32Q14_1, expL32Q14_2; +#if ((defined __hexagon__) || (defined __qdsp6__)) + SoftVolumeControlsExp2Fixed_asm(CL32Q26_1, CL32Q26_2, &expL32Q14_1, &expL32Q14_2); +#else + SoftVolumeControlsExp2Fixed(CL32Q26_1, CL32Q26_2, &expL32Q14_1, &expL32Q14_2); +#endif + + int64 product1L64Q40 = s64_mult_s32_s32(AL32Q26, expL32Q14_1); + int64 product2L64Q40 = s64_mult_s32_s32(AL32Q26, expL32Q14_2); + int64 product1L64Q26 = (product1L64Q40 >> (40 - 26)); + int64 product2L64Q26 = (product2L64Q40 >> (40 - 26)); + + int32 product1L32Q26 = s32_saturate_s64(product1L64Q26); + int32 product2L32Q26 = s32_saturate_s64(product2L64Q26); + + int32 gainL32Q26 = BL32Q26 + product1L32Q26; + int32 gain2L32Q26 = BL32Q26 + product2L32Q26; + + if (gainL32Q26 > 0) + { + currentGainL32Q28 = gainL32Q26 << (28 - 26); + } + else + { + currentGainL32Q28 = 0; + } + + int32 input = *pInput++; + int32 input2 = *pInput++; + + int64 product; + + product = (int64)input * currentGainL32Q28; + + if (gain2L32Q26 > 0) + { + currentGainL32Q28 = gain2L32Q26 << (28 - 26); + } + else + { + currentGainL32Q28 = 0; + } + int64 product2; + + product2 = (int64)input2 * currentGainL32Q28; + + product = s64_shr_s64_imm5_rnd(product, 28); + product2 = s64_shr_s64_imm5_rnd(product2, 28); + + *pOutput++ = s16_saturate_s32(product); + *pOutput++ = s16_saturate_s32(product2); + samples -= 2; + } + } + + // remaining samples + while (samples--) + { + CL32Q26_2 += deltaCL32Q26; + + int32 expL32Q14 = SoftVolumeControlsExp2FixedSingle(CL32Q26_2); + int64 productL64Q40 = s64_mult_s32_s32(AL32Q26, expL32Q14); + + int64 productL64Q26 = (productL64Q40 >> (40 - 26)); + + int32 productL32Q26 = s32_saturate_s64(productL64Q26); + + int32 gainL32Q26 = BL32Q26 + productL32Q26; + if (gainL32Q26 > 0) + { + currentGainL32Q28 = gainL32Q26 << (28 - 26); + } + else + { + currentGainL32Q28 = 0; + } + + int32 input = *pInput++; + int64 product; + if ((currentGainL32Q28 < (uint32)0x80000000)) + { + product = s64_mult_s32_s32(input, currentGainL32Q28); + } + else + { + product = (int64)input * currentGainL32Q28; + } + + product = s64_shr_s64_imm5_rnd(product, 28); + *pOutput++ = s16_saturate_s32(product); + } + + panner->coeffs.exp.AL32Q26 = AL32Q26; + panner->coeffs.exp.BL32Q26 = BL32Q26; + panner->coeffs.exp.CL32Q26 = CL32Q26_2; + panner->coeffs.exp.deltaCL32Q26 = deltaCL32Q26; + panner->step = step; + panner->stepResidue = stepResidue; + panner->currentGainL32Q28 = currentGainL32Q28; +} + +void ApplyExpRamp32bitStep1(void *pOutPtr, void *pInPtr, SvpannerStruct *panner, uint32 samples) +{ + int32 AL32Q26 = panner->coeffs.exp.AL32Q26; + int32 BL32Q26 = panner->coeffs.exp.BL32Q26; + + int32 CL32Q26_1 = panner->coeffs.exp.CL32Q26; + int32 CL32Q26_2 = panner->coeffs.exp.CL32Q26; + int32 deltaCL32Q26 = panner->coeffs.exp.deltaCL32Q26; + uint32 step = panner->step; + uint32 stepResidue = panner->stepResidue; + uint32 currentGainL32Q28 = panner->currentGainL32Q28; + + int32 *pInput = (int32 *)(pInPtr); + int32 *pOutput = (int32 *)(pOutPtr); + + if (0 != stepResidue) + { + + int32 input = *pInput++; + int64 product; + if ((currentGainL32Q28 < (uint32)0x80000000)) + { + product = s64_mult_s32_s32(input, currentGainL32Q28); + } + else + { + product = (int64)input * currentGainL32Q28; + } + + product = s64_shr_s64_imm5_rnd(product, 28); + *pOutput++ = s32_saturate_s64(product); + + stepResidue = 0; + samples--; + } + + if ((currentGainL32Q28 < (uint32)0x80000000)) + { + // loop unrolled by 2 + while (samples >= 2) + { + // Calculate the new gain + CL32Q26_1 = CL32Q26_2 + deltaCL32Q26; + CL32Q26_2 = CL32Q26_1 + deltaCL32Q26; + + int32 expL32Q14_1, expL32Q14_2; + +#if ((defined __hexagon__) || (defined __qdsp6__)) + SoftVolumeControlsExp2Fixed_asm(CL32Q26_1, CL32Q26_2, &expL32Q14_1, &expL32Q14_2); +#else + SoftVolumeControlsExp2Fixed(CL32Q26_1, CL32Q26_2, &expL32Q14_1, &expL32Q14_2); +#endif + + int64 product1L64Q40 = s64_mult_s32_s32(AL32Q26, expL32Q14_1); + int64 product2L64Q40 = s64_mult_s32_s32(AL32Q26, expL32Q14_2); + int64 product1L64Q26 = (product1L64Q40 >> (40 - 26)); + int64 product2L64Q26 = (product2L64Q40 >> (40 - 26)); + + int32 product1L32Q26 = s32_saturate_s64(product1L64Q26); + int32 product2L32Q26 = s32_saturate_s64(product2L64Q26); + + int32 gainL32Q26 = BL32Q26 + product1L32Q26; + int32 gain2L32Q26 = BL32Q26 + product2L32Q26; + + if (gainL32Q26 > 0) + { + currentGainL32Q28 = gainL32Q26 << (28 - 26); + } + else + { + currentGainL32Q28 = 0; + } + + int32 input = *pInput++; + int32 input2 = *pInput++; + int64 product; + + product = s64_mult_s32_s32(input, currentGainL32Q28); + + if (gain2L32Q26 > 0) + { + currentGainL32Q28 = gain2L32Q26 << (28 - 26); + } + else + { + currentGainL32Q28 = 0; + } + int64 product2; + + product2 = s64_mult_s32_s32(input2, currentGainL32Q28); + + product = s64_shr_s64_imm5_rnd(product, 28); + product2 = s64_shr_s64_imm5_rnd(product2, 28); + + *pOutput++ = s32_saturate_s64(product); + *pOutput++ = s32_saturate_s64(product2); + samples -= 2; + } + } + else + { + // loop unrolled by 2 + while (samples >= 2) + { + // Calculate the new gain + CL32Q26_1 = CL32Q26_2 + deltaCL32Q26; + CL32Q26_2 = CL32Q26_1 + deltaCL32Q26; + + int32 expL32Q14_1, expL32Q14_2; + +#if ((defined __hexagon__) || (defined __qdsp6__)) + SoftVolumeControlsExp2Fixed_asm(CL32Q26_1, CL32Q26_2, &expL32Q14_1, &expL32Q14_2); +#else + SoftVolumeControlsExp2Fixed(CL32Q26_1, CL32Q26_2, &expL32Q14_1, &expL32Q14_2); +#endif + + int64 product1L64Q40 = s64_mult_s32_s32(AL32Q26, expL32Q14_1); + int64 product2L64Q40 = s64_mult_s32_s32(AL32Q26, expL32Q14_2); + int64 product1L64Q26 = (product1L64Q40 >> (40 - 26)); + int64 product2L64Q26 = (product2L64Q40 >> (40 - 26)); + + int32 product1L32Q26 = s32_saturate_s64(product1L64Q26); + int32 product2L32Q26 = s32_saturate_s64(product2L64Q26); + + int32 gainL32Q26 = BL32Q26 + product1L32Q26; + int32 gain2L32Q26 = BL32Q26 + product2L32Q26; + + if (gainL32Q26 > 0) + { + currentGainL32Q28 = gainL32Q26 << (28 - 26); + } + else + { + currentGainL32Q28 = 0; + } + + int32 input = *pInput++; + int32 input2 = *pInput++; + int64 product; + + product = (int64)input * currentGainL32Q28; + + if (gain2L32Q26 > 0) + { + currentGainL32Q28 = gain2L32Q26 << (28 - 26); + } + else + { + currentGainL32Q28 = 0; + } + int64 product2; + + product2 = (int64)input2 * currentGainL32Q28; + + product = s64_shr_s64_imm5_rnd(product, 28); + product2 = s64_shr_s64_imm5_rnd(product2, 28); + + *pOutput++ = s32_saturate_s64(product); + *pOutput++ = s32_saturate_s64(product2); + samples -= 2; + } + } + + // remaining samples + while (samples--) + { + // Calculate the new gain + CL32Q26_2 += deltaCL32Q26; + + int32 expL32Q14 = SoftVolumeControlsExp2FixedSingle(CL32Q26_2); + int64 productL64Q40 = s64_mult_s32_s32(AL32Q26, expL32Q14); + + int64 productL64Q26 = (productL64Q40 >> (40 - 26)); + + int32 productL32Q26 = s32_saturate_s64(productL64Q26); + + int32 gainL32Q26 = BL32Q26 + productL32Q26; + if (gainL32Q26 > 0) + { + currentGainL32Q28 = gainL32Q26 << (28 - 26); + } + else + { + currentGainL32Q28 = 0; + } + + int32 input = *pInput++; + int64 product; + if ((currentGainL32Q28 < (uint32)0x80000000)) + { + product = s64_mult_s32_s32(input, currentGainL32Q28); + } + else + { + product = (int64)input * currentGainL32Q28; + } + + product = s64_shr_s64_imm5_rnd(product, 28); + *pOutput++ = s32_saturate_s64(product); + } + + panner->coeffs.exp.AL32Q26 = AL32Q26; + panner->coeffs.exp.BL32Q26 = BL32Q26; + panner->coeffs.exp.CL32Q26 = CL32Q26_2; + panner->coeffs.exp.deltaCL32Q26 = deltaCL32Q26; + panner->step = step; + panner->stepResidue = stepResidue; + panner->currentGainL32Q28 = currentGainL32Q28; +} + +/*============================================================================= +FUNCTION CSoftVolumeControlsLib::ApplyExpRamp + +DESCRIPTION increase gain(Q12) in a exponential curve and apply to the input(Q15) and +save to output (Q15) +OUTPUTS + +DEPENDENCIES None + +RETURN VALUE None + +SIDE EFFECTS +===============================================================================*/ +void CSoftVolumeControlsLib::ApplyExpRamp(void *pOutPtr, void *pInPtr, SvpannerStruct *panner, uint32 samples) +{ + uint32 i; + int32 AL32Q26 = panner->coeffs.exp.AL32Q26; + int32 BL32Q26 = panner->coeffs.exp.BL32Q26; + int32 CL32Q26 = panner->coeffs.exp.CL32Q26; + int32 deltaCL32Q26 = panner->coeffs.exp.deltaCL32Q26; + uint32 step = panner->step; + uint32 stepResidue = panner->stepResidue; + uint32 currentGainL32Q28 = panner->currentGainL32Q28; + + if (step <= 1) + { + if (m_bytesPerSample == 2) + { + ApplyExpRamp16bitStep1(pOutPtr, pInPtr, panner, samples); + + } // if + else // (m_bytesPerSample == 4) + { + ApplyExpRamp32bitStep1(pOutPtr, pInPtr, panner, samples); + } + } + else + { + for (i = samples; i > 0;) + { + if (0 == stepResidue) + { + // Calculate the new gain + CL32Q26 += deltaCL32Q26; + + int32 expL32Q14 = SoftVolumeControlsExp2FixedSingle(CL32Q26); + int64 productL64Q40 = ((int64)(AL32Q26)) * expL32Q14; + int64 productL64Q26 = (productL64Q40 >> (40 - 26)); + + int32 productL32Q26 = s32_saturate_s64(productL64Q26); + + int32 gainL32Q26 = BL32Q26 + productL32Q26; + if (gainL32Q26 > 0) + { + currentGainL32Q28 = gainL32Q26 << (28 - 26); + } + else + { + currentGainL32Q28 = 0; + } + + stepResidue = step; + } + + uint32 deltaStep = (i < stepResidue) ? i : stepResidue; + + ApplySteadyGain(pOutPtr, pInPtr, currentGainL32Q28, deltaStep); + + IncrementPointer(&pInPtr, deltaStep); + IncrementPointer(&pOutPtr, deltaStep); + + i -= deltaStep; + stepResidue -= deltaStep; + } + + panner->coeffs.exp.AL32Q26 = AL32Q26; + panner->coeffs.exp.BL32Q26 = BL32Q26; + panner->coeffs.exp.CL32Q26 = CL32Q26; + panner->coeffs.exp.deltaCL32Q26 = deltaCL32Q26; + panner->step = step; + panner->stepResidue = stepResidue; + panner->currentGainL32Q28 = currentGainL32Q28; + } +} + +/*============================================================================= +FUNCTION CSoftVolumeControlsLib::Process + +DESCRIPTION Process function is called by enhancedconvert and applies gain to the +input samples. The gain can also ramp up/down in a linear/log/exp curve +as well. +OUTPUTS + +DEPENDENCIES None + +RETURN VALUE None + +SIDE EFFECTS +===============================================================================*/ +void CSoftVolumeControlsLib::Process(void *pInPtr, void *pOutPtr, const uint32 nSampleCnt, void *pChannelStruct) +{ + + perChannelData *pChannelData = reinterpret_cast(pChannelStruct); + + SvpannerStruct *pPanner = &pChannelData->panner; + uint32 samples = nSampleCnt; + if (0 == samples) + { + return; + } + if (pPanner->sampleCounter > 0) + { + // Soft stepping is needed + uint32 rampSamples; + rampSamples = (samples < pPanner->sampleCounter) ? samples : pPanner->sampleCounter; + + switch (pPanner->rampingCurve) + { + case RAMP_LINEAR: + ApplyLinearRamp(pOutPtr, pInPtr, pPanner, rampSamples); + break; + case RAMP_LOG: + ApplyLogRamp(pOutPtr, pInPtr, pPanner, rampSamples); + break; + case RAMP_EXP: + ApplyExpRamp(pOutPtr, pInPtr, pPanner, rampSamples); + break; + case RAMP_FRACT_EXP: + ApplyFractExpRamp(pOutPtr, pInPtr, pPanner, rampSamples); + break; + } + + IncrementPointer(&pInPtr, rampSamples); + IncrementPointer(&pOutPtr, rampSamples); + + samples -= rampSamples; + pPanner->sampleCounter -= rampSamples; + + if (pPanner->sampleCounter <= 0) + { + pPanner->currentGainL32Q28 = pPanner->targetgainL32Q28; + } + } + + /*-------------- if there are still samples , apply static gain-------*/ + ApplySteadyGain(pOutPtr, pInPtr, pPanner->currentGainL32Q28, samples); + +} /*------------------- end of function Process-----------------------------------*/ + +boolean CSoftVolumeControlsLib::ProcessV2SingleChannel(void * pInPtr, + void * pOutPtr, + const uint32 nSampleCnt, + void * pChannelStruct) +{ + boolean ramp_complete = false; + perChannelData *pChannelData = reinterpret_cast(pChannelStruct); + SvpannerStruct *pPanner = &pChannelData->panner; + uint32 samples = nSampleCnt; + + if (0 == samples) + { + return false; + } + + if (pPanner->sampleCounter > 0) + { + // Soft stepping is needed + uint32 rampSamples; + rampSamples = (samples < pPanner->sampleCounter) ? samples : pPanner->sampleCounter; + + switch (pPanner->rampingCurve) + { + case RAMP_LINEAR: + ApplyLinearRamp(pOutPtr, pInPtr, pPanner, rampSamples); + break; + case RAMP_LOG: + ApplyLogRamp(pOutPtr, pInPtr, pPanner, rampSamples); + break; + case RAMP_EXP: + ApplyExpRamp(pOutPtr, pInPtr, pPanner, rampSamples); + break; + case RAMP_FRACT_EXP: + ApplyFractExpRamp(pOutPtr, pInPtr, pPanner, rampSamples); + break; + } + + IncrementPointer(&pInPtr, rampSamples); + IncrementPointer(&pOutPtr, rampSamples); + + samples -= rampSamples; + pPanner->sampleCounter -= rampSamples; + + if (0 == pPanner->sampleCounter) + { + pPanner->currentGainL32Q28 = pPanner->targetgainL32Q28; + ramp_complete = true; + } + } + else + { + ramp_complete = true; + } + + /*-------------- if there are still samples , apply static gain-------*/ + ApplySteadyGain(pOutPtr, pInPtr, pPanner->currentGainL32Q28, samples); + + return ramp_complete; +} + +void CSoftVolumeControlsLib::ProcessAllChannels(PerChannelDataBlock *pChannels, uint32 nChannelCnt, uint8_t *is_paused) +{ + uint32 pannerOffset = 0xFFFFFF; + boolean curRampCompleted = false; + boolean rampCompleted = false; + + // For steady state check if in-place, if yes return, if not, copy data and return + if (STEADY == m_pauseState) + { + for (uint32_t channel_idx = 0; channel_idx < nChannelCnt; channel_idx++) + { + PerChannelDataBlock channelData = pChannels[channel_idx]; + if (channelData.inPtr != channelData.outPtr) + { + memscpy(channelData.outPtr, + (channelData.sampleCount * m_bytesPerSample), + channelData.inPtr, + (channelData.sampleCount * m_bytesPerSample)); + } + } + return; + } + + // If Pause, zero fill and return. + if (PAUSE == m_pauseState) + { + for (uint32_t channel_idx = 0; channel_idx < nChannelCnt; channel_idx++) + { + PerChannelDataBlock channelData = pChannels[channel_idx]; + memset(channelData.outPtr, 0, (channelData.sampleCount * m_bytesPerSample)); + } + return; + } + + // 1.Search for the earliest time a threshold was detected. If no threshold is detected, fill with zeros and then + // return. + // 2. If it is detected find the sample and apply 0 till that sample and start ramp after + if (WAITING == m_pauseState) + { + // find earliest sample over all channels that hits threshold value + for (int channel_idx = 0; channel_idx < nChannelCnt; channel_idx++) + { + PerChannelDataBlock channelData = pChannels[channel_idx]; + + int curOffset = DetectThreshold(channelData.inPtr, channelData.sampleCount); + if (THRESHOLD_NOT_DETECTED != curOffset) + { + // if current offset is lesser, set as new panner offset + if (curOffset < pannerOffset) + { + pannerOffset = curOffset; + } + } + } + + // If threshold not reached, memset to zero and return + if (0xFFFFFF == pannerOffset) + { + for (int channel_idx = 0; channel_idx < nChannelCnt; channel_idx++) + { + PerChannelDataBlock channelData = pChannels[channel_idx]; + memset(channelData.outPtr, 0, (channelData.sampleCount * m_bytesPerSample)); + } + return; + } + m_muteBeforeResumeParams.m_muteSamplesPending += pannerOffset; + } + + if ((MUTE_BEFORE_RAMPUP == m_pauseState) | (WAITING == m_pauseState)) + { + uint32_t mute_samples_pending = 0; + for (int channel_idx = 0; channel_idx < nChannelCnt; channel_idx++) + { + mute_samples_pending = m_muteBeforeResumeParams.m_muteSamplesPending; + PerChannelDataBlock *channelData = &pChannels[channel_idx]; + if (mute_samples_pending < channelData->sampleCount) + { + memset(channelData->outPtr, 0, (mute_samples_pending * m_bytesPerSample)); + channelData->sampleCount = channelData->sampleCount - mute_samples_pending; + channelData->inPtr = channelData->inPtr + (mute_samples_pending * m_bytesPerSample); + channelData->outPtr = channelData->outPtr + (mute_samples_pending * m_bytesPerSample); + curRampCompleted = ProcessV2SingleChannel(channelData->inPtr, + channelData->outPtr, + channelData->sampleCount, + (void *)&(channelData->channelStruct)); + mute_samples_pending = 0; + } + else + { + memset(channelData->outPtr, 0, channelData->sampleCount * m_bytesPerSample); + mute_samples_pending = mute_samples_pending - channelData->sampleCount; + } + } + m_muteBeforeResumeParams.m_muteSamplesPending = mute_samples_pending; + if (m_muteBeforeResumeParams.m_muteSamplesPending) + { + m_pauseState = MUTE_BEFORE_RAMPUP; + } + else + { + m_pauseState = RAMPING_UP; + } + return; + } + + // If ramping up or ramping down or pending dfg, process data by applying ramp + for (uint32_t channel_idx = 0; channel_idx < nChannelCnt; channel_idx++) + { + + PerChannelDataBlock *channelData = &pChannels[channel_idx]; + curRampCompleted = ProcessV2SingleChannel(channelData->inPtr, + channelData->outPtr, + channelData->sampleCount, + (void *)&(channelData->channelStruct)); + + rampCompleted = (rampCompleted || curRampCompleted); + } + + // If ramp completed, change state and set flag to paused + if (rampCompleted) + { + if (m_pauseState == RAMPING_UP) + { + m_pauseState = STEADY; + } + else if (m_pauseState == RAMPING_DOWN) + { + // Set flag + *is_paused = PAUSE_RAMP_COMPLETE; + m_pauseState = PAUSE; + } + } +} + +int32 CSoftVolumeControlsLib::DetectThreshold(void *pInPtr, const uint32 nSampleCnt) +{ + int32 threshold_idx = THRESHOLD_NOT_DETECTED; + + if (m_qFactor == 15) + { + threshold_idx = DetectThreshold16(pInPtr, nSampleCnt); + } + else if (m_qFactor == 27) + { + threshold_idx = DetectThreshold24(pInPtr, nSampleCnt); + } + else if (m_qFactor == 31) + { + threshold_idx = DetectThreshold32(pInPtr, nSampleCnt); + } + + return threshold_idx; +} + +int32 CSoftVolumeControlsLib::DetectThreshold16(void *pInPtr, const uint32 nSampleCnt) +{ + int16 *pInput = (int16 *)pInPtr; + int32 threshold_idx = THRESHOLD_NOT_DETECTED; + + for (uint32_t sample_idx = 0; sample_idx < nSampleCnt; sample_idx++) + { + int32 sampleQ15 = pInput[sample_idx]; + uint32 abs_sampleQ15 = (uint32_t)u16_abs_s16_sat((int16_t)sampleQ15); + if (abs_sampleQ15 > m_thresholdQ15) + { + + threshold_idx = sample_idx; + break; + } + } + return threshold_idx; +} + +int32 CSoftVolumeControlsLib::DetectThreshold24(void *pInPtr, const uint32 nSampleCnt) +{ + int16 *pInput = (int16 *)pInPtr; + int32 threshold_idx = THRESHOLD_NOT_DETECTED; + + for (uint32_t sample_idx = 0; sample_idx < nSampleCnt; sample_idx++) + { + int32 sampleQ27 = pInput[sample_idx]; + uint32 abs_sampleQ27 = u32_abs_s32_sat(sampleQ27); + if (abs_sampleQ27 > m_thresholdQ27) + { + threshold_idx = sample_idx; + break; + } + } + return threshold_idx; +} + +int32 CSoftVolumeControlsLib::DetectThreshold32(void *pInPtr, const uint32 nSampleCnt) +{ + int32 *pInput = (int32 *)pInPtr; + int32 threshold_idx = THRESHOLD_NOT_DETECTED; + + for (uint32_t sample_idx = 0; sample_idx < nSampleCnt; sample_idx++) + { + int32 sampleQ31 = pInput[sample_idx]; + uint32 abs_sampleQ31 = u32_abs_s32_sat(sampleQ31); + + if (abs_sampleQ31 > m_thresholdQ31) + { + threshold_idx = sample_idx; + break; + } + } + return threshold_idx; +} + +uint32 CSoftVolumeControlsLib::GetPauseState() const +{ + return (uint32_t)m_pauseState; +} + +uint32 CSoftVolumeControlsLib::GetTargetGain(void *pChannelStruct) +{ + perChannelData *pChannelData = reinterpret_cast(pChannelStruct); + return pChannelData->panner.targetgainL32Q28; +} + +void CSoftVolumeControlsLib::ApplySteadyGain(void *pOutPtr, void *pInPtr, const uint32 gainQ28, uint32 samples) +{ + switch (m_bytesPerSample) + { + case 2: +#if ((defined __hexagon__) || (defined __qdsp6__)) + if (gainQ28 < UNITY_L32_Q28) + { + ApplySteadyGainLessThanOne16(pOutPtr, pInPtr, gainQ28, samples); + } + else +#endif + ApplySteadyGain16(pOutPtr, pInPtr, gainQ28, samples); + break; + + case 4: + ApplySteadyGain32(pOutPtr, pInPtr, gainQ28, samples); + break; + } +} + +void CSoftVolumeControlsLib::ApplyFractExpRamp(void *pOutPtr, void *pInPtr, SvpannerStruct *panner, uint32 samples) +{ + uint32 i; + uint32 step = panner->step; + uint32 stepResidue = panner->stepResidue; + uint32 currentGainL32Q28 = panner->currentGainL32Q28; + uint32 AL32Q28 = panner->coeffs.fract.AL32Q28; + uint32 BL32Q28 = panner->coeffs.fract.BL32Q28; + uint32 CL32Q31 = panner->coeffs.fract.CL32Q31; + int32 deltaCL32Q31 = panner->coeffs.fract.deltaCL32Q31; + + for (i = samples; i > 0;) + { + if (0 == stepResidue) + { + // Calculate the new gain + currentGainL32Q28 = AL32Q28 + u32_mult_u32_u32(BL32Q28, pow_1_75(CL32Q31) << 1); + CL32Q31 += deltaCL32Q31; + stepResidue = step; + } + + uint32 deltaStep = (i < stepResidue) ? i : stepResidue; + + ApplySteadyGain(pOutPtr, pInPtr, currentGainL32Q28, deltaStep); + + IncrementPointer(&pInPtr, deltaStep); + IncrementPointer(&pOutPtr, deltaStep); + + i -= deltaStep; + stepResidue -= deltaStep; + panner->index += deltaStep; + } + + panner->step = step; + panner->coeffs.fract.CL32Q31 = CL32Q31; + panner->stepResidue = stepResidue; + panner->currentGainL32Q28 = currentGainL32Q28; +} + +void CSoftVolumeControlsLib::IncrementPointer(void **pPtr, uint32 samples) +{ + uint32 numBytes = samples * m_bytesPerSample; + byte * ptr = (byte *)(*pPtr); + ptr += numBytes; + *pPtr = ptr; +} + +void CSoftVolumeControlsLib::ApplySteadyGain16(void *pOutPtr, void *pInPtr, const uint32 gainQ28, uint32 samples) +{ + + int16 *pInput = (int16 *)(pInPtr); + int16 *pOutput = (int16 *)(pOutPtr); + + if (gainQ28 < (uint32)0x80000000) + { + // unroll loop by 2 + while (samples >= 2) + { + int32 input1 = *pInput++; + int32 input2 = *pInput++; + int64 product1, product2; + + product1 = s64_mult_s32_s32(input1, gainQ28); + product2 = s64_mult_s32_s32(input2, gainQ28); + product1 = s64_shr_s64_imm5_rnd(product1, 28); + product2 = s64_shr_s64_imm5_rnd(product2, 28); + *pOutput++ = s16_saturate_s32(product1); + *pOutput++ = s16_saturate_s32(product2); + samples -= 2; + } + + while (samples--) + { + int32 input1 = *pInput++; + + int64 product1; + + product1 = s64_mult_s32_s32(input1, gainQ28); + + product1 = s64_shr_s64_imm5_rnd(product1, 28); + *pOutput++ = s16_saturate_s32(product1); + } + } + else + { + // unroll loop by 2 + while (samples >= 2) + { + int32 input1 = *pInput++; + int32 input2 = *pInput++; + int64 product1, product2; + + // int64 input = pInput[i]; + product1 = (int64)input1 * gainQ28; + product2 = (int64)input2 * gainQ28; + + product1 = s64_shr_s64_imm5_rnd(product1, 28); + product2 = s64_shr_s64_imm5_rnd(product2, 28); + *pOutput++ = s16_saturate_s32(product1); + *pOutput++ = s16_saturate_s32(product2); + samples -= 2; + } + + while (samples--) + { + int32 input1 = *pInput++; + + int64 product1; + + product1 = (int64)input1 * gainQ28; + product1 = s64_shr_s64_imm5_rnd(product1, 28); + *pOutput++ = s16_saturate_s32(product1); + } + } +} + +void CSoftVolumeControlsLib::ApplySteadyGain32(void *pOutPtr, void *pInPtr, const uint32 gainQ28, uint32 samples) +{ + int32 *pInput = (int32 *)(pInPtr); + int32 *pOutput = (int32 *)(pOutPtr); + + if (gainQ28 < (uint32)0x80000000) + { + // unroll loop by 2 + while (samples >= 2) + { + int32 input1 = *pInput++; + int32 input2 = *pInput++; + + int64 product1; + int64 product2; + + product1 = s64_mult_s32_s32(input1, gainQ28); + product2 = s64_mult_s32_s32(input2, gainQ28); + + product1 = s64_shr_s64_imm5_rnd(product1, 28); + product2 = s64_shr_s64_imm5_rnd(product2, 28); + *pOutput++ = s32_saturate_s64(product1); + *pOutput++ = s32_saturate_s64(product2); + samples -= 2; + } + + while (samples--) + { + int32 input1 = *pInput++; + + int64 product1; + + product1 = s64_mult_s32_s32(input1, gainQ28); + + product1 = s64_shr_s64_imm5_rnd(product1, 28); + *pOutput++ = s32_saturate_s64(product1); + } + } + else // gainQ28 >= 0x80000000 + { + // unroll loop by 2 + while (samples >= 2) + { + int32 input1 = *pInput++; + int32 input2 = *pInput++; + + int64 product1; + int64 product2; + + // int64 input = pInput[i]; + product1 = (int64)input1 * gainQ28; + product2 = (int64)input2 * gainQ28; + + product1 = s64_shr_s64_imm5_rnd(product1, 28); + product2 = s64_shr_s64_imm5_rnd(product2, 28); + *pOutput++ = s32_saturate_s64(product1); + *pOutput++ = s32_saturate_s64(product2); + samples -= 2; + } + + while (samples--) + { + int32 input1 = *pInput++; + + int64 product1; + + product1 = (int64)input1 * gainQ28; + + product1 = s64_shr_s64_imm5_rnd(product1, 28); + *pOutput++ = s32_saturate_s64(product1); + } + } +} + +// Look up table for the function pow_1_75. +const int32_t coeffs_1_75[64][4] = { + { -1164088560, 313392297, 1319088, 0 }, { -1164088560, 258825646, 10259994, 92682 }, + { -307879499, 204258995, 17495691, 311744 }, { -303631637, 189827143, 23653287, 633807 }, + { -193864184, 175594410, 29362999, 1048576 }, { -159201065, 166507027, 34708334, 1549503 }, + { -127113798, 159044477, 39795076, 2131865 }, { -106977359, 153086017, 44672115, 2792007 }, + { -91359926, 148071454, 49377700, 3526975 }, { -79572318, 143788957, 53938019, 4334304 }, + { -70219336, 140059005, 58373144, 5211886 }, { -62686037, 136767473, 62698558, 6157893 }, + { -56488224, 133829065, 66926628, 7170709 }, { -51312925, 131181180, 71067413, 8248895 }, + { -46932167, 128775886, 75129243, 9391155 }, { -43181370, 126575941, 79119115, 10596309 }, + { -39937509, 124551814, 83042986, 11863283 }, { -37107280, 122679744, 86905979, 13191086 }, + { -34618601, 120940340, 90712543, 14578801 }, { -32414981, 119317593, 94466573, 16025579 }, + { -30451554, 117798141, 98171507, 17530626 }, { -28692258, 116370724, 101830395, 19093199 }, + { -27107797, 115025775, 105445965, 20712600 }, { -25674141, 113755097, 109020667, 22388172 }, + { -24371402, 112551621, 112556709, 24119295 }, { -23182988, 111409212, 116056097, 25905379 }, + { -22094954, 110322509, 119520655, 27745866 }, { -21095496, 109286808, 122952051, 29640226 }, + { -20174565, 108297957, 126351813, 31587953 }, { -19323550, 107352274, 129721348, 33588563 }, + { -18535035, 106446483, 133061953, 35641594 }, { -17802600, 105577653, 136374830, 37746605 }, + { -17120661, 104743156, 139661093, 39903169 }, { -16484340, 103940625, 142921777, 42110881 }, + { -15889356, 103167922, 146157848, 44369347 }, { -15331939, 102423108, 149370208, 46678190 }, + { -14808758, 101704424, 152559701, 49037047 }, { -14316856, 101010263, 155727118, 51445566 }, + { -13853601, 100339160, 158873202, 53903408 }, { -13416645, 99689773, 161998654, 56410246 }, + { -13003885, 99060868, 165104133, 58965762 }, { -12613431, 98451311, 168190261, 61569649 }, + { -12243584, 97860056, 171257626, 64221610 }, { -11892809, 97286138, 174306785, 66921355 }, + { -11559717, 96728663, 177338267, 69668605 }, { -11243049, 96186801, 180352571, 72463086 }, + { -10941659, 95659783, 183350173, 75304536 }, { -10654507, 95146893, 186331528, 78192695 }, + { -10380640, 94647463, 189297065, 81127313 }, { -10119188, 94160870, 192247195, 84108148 }, + { -9869354, 93686533, 195182310, 87134960 }, { -9630408, 93223907, 198102786, 90207519 }, + { -9401677, 92772482, 201008980, 93325598 }, { -9182542, 92331778, 203901234, 96488977 }, + { -8972431, 91901347, 206779876, 99697440 }, { -8770823, 91480764, 209645222, 102950779 }, + { -8577206, 91069632, 212497572, 106248786 }, { -8391217, 90667575, 215337216, 109591261 }, + { -8212108, 90274237, 218164431, 112978009 }, { -8040720, 89889294, 220979487, 116408837 }, + { -7872098, 89512385, 223782638, 119883556 }, { -7723049, 89143381, 226574134, 123401983 }, + { -7529275, 88781363, 229354208, 126963938 }, { -7529275, 88428428, 232123111, 130569244 }, +}; + +/* + * Implements the function x^1.75. + * + * Input: + * x : Input value in Q31 format. 0 <= x < 1. + * + * Return value: + * x^1.75 in Q31 format. + * + * Implementation details: + * The function is implemented using spline interpolation. + * MATLAB was used to generate the spline coefficients using + * 64 intervals from 0 to 1. These coefficients are stored in + * Q27 format (since the largest coefficient had magnitude about + * 8.6). The coefficients are stored in a 2 dimensional array. + * The first index is the interval number. The top 6 bits + * of the input data can be used to get this number. The + * rest of the bits will be the offset z to be plugged into + * y = a*z^3 + b*z^2 + c*z + d + * to get the final result y. + */ +static uint32_t pow_1_75(uint32_t x) +{ + const uint32_t DATA_Q_FACTOR = 31; + const uint32_t COEFF_Q_FACTOR = 27; + const uint32_t NUM_INDEX_BITS = 6; + const uint32_t Q_FACTOR_FOR_MULTIPLICATION = 31; + + uint32_t index = x >> (DATA_Q_FACTOR - NUM_INDEX_BITS); + int32_t offset = x & ((1 << (DATA_Q_FACTOR - NUM_INDEX_BITS)) - 1); + + int32_t z = offset; + int32_t z2 = s32_mult_s32_s32_left_shift_1(z, z); + int32_t z3 = s32_mult_s32_s32_left_shift_1(z2, z); + + int32_t y = (coeffs_1_75[index][3] + s32_mult_s32_s32_left_shift_1(coeffs_1_75[index][2], z) + + s32_mult_s32_s32_left_shift_1(coeffs_1_75[index][1], z2) + s32_mult_s32_s32_left_shift_1(coeffs_1_75[index][0], z3)) + << (Q_FACTOR_FOR_MULTIPLICATION - COEFF_Q_FACTOR); + + return ((y > 0) ? y : 0); +} From 36e035d71a84c912fbcbba7a529b87cdcc742a5c Mon Sep 17 00:00:00 2001 From: Annemarie Porter Date: Tue, 22 Apr 2025 16:07:13 -0700 Subject: [PATCH 3/3] modules: Update copyrights for api files Change copyrights for api files to BSD-Clause-Clear. Signed-off-by: Annemarie Porter --- modules/cmn/common/internal_api/imcl_module_gain_api.h | 2 +- modules/cmn/common/internal_api/imcl_mute_api.h | 2 +- modules/cmn/common/internal_api/imcl_p_eq_vol_api.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/cmn/common/internal_api/imcl_module_gain_api.h b/modules/cmn/common/internal_api/imcl_module_gain_api.h index 46c1a39..6716e4e 100644 --- a/modules/cmn/common/internal_api/imcl_module_gain_api.h +++ b/modules/cmn/common/internal_api/imcl_module_gain_api.h @@ -11,7 +11,7 @@ /*========================================================================== * Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. - * SPDX-License-Identifier: BSD-3-Clause + * SPDX-License-Identifier: BSD-3-Clause-Clear * =========================================================================*/ #define INTENT_ID_GAIN_INFO 0x080010F5 diff --git a/modules/cmn/common/internal_api/imcl_mute_api.h b/modules/cmn/common/internal_api/imcl_mute_api.h index 9fabd3f..153309d 100644 --- a/modules/cmn/common/internal_api/imcl_mute_api.h +++ b/modules/cmn/common/internal_api/imcl_mute_api.h @@ -11,7 +11,7 @@ /*========================================================================== * Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. - * SPDX-License-Identifier: BSD-3-Clause + * SPDX-License-Identifier: BSD-3-Clause-Clear * =========================================================================*/ #define INTENT_ID_MUTE 0x08001195 diff --git a/modules/cmn/common/internal_api/imcl_p_eq_vol_api.h b/modules/cmn/common/internal_api/imcl_p_eq_vol_api.h index 9e2247d..f400766 100644 --- a/modules/cmn/common/internal_api/imcl_p_eq_vol_api.h +++ b/modules/cmn/common/internal_api/imcl_p_eq_vol_api.h @@ -9,7 +9,7 @@ /*========================================================================== Copyright (c) Qualcomm Innovation Center, Inc. All Rights Reserved. - SPDX-License-Identifier: BSD-3-Clause + SPDX-License-Identifier: BSD-3-Clause-Clear * =========================================================================*/ #define INTENT_ID_P_EQ_VOL_HEADROOM 0x08001118